本篇的自定义KVO(不通过中间类)与广为流传的KVO(通过中间类)版本有些许不同,
当然,最主要的是KVO的实现原理,所以代码有点粗糙
自定义类
自定义测试类
@interface Honzon : NSObject
@property (nonatomic, copy)NSString *test;
@end
@interface Honzon ()
@end
@implementation Honzon
- (void)dealloc {
NSLog(@"Honzon dealloc");
}
@end
自定义KVO信息保存类
key
保存属性名称
block
保存回调
@interface HZKVOInfo : NSObject
@property (nonatomic, copy)NSString *key;
@property (nonatomic, copy)HZ_KVOBlock block;
- (instancetype)initWithKey:(NSString *)key block:(HZ_KVOBlock)block;
@end
@implementation HZKVOInfo
- (instancetype)initWithKey:(NSString *)key block:(HZ_KVOBlock)block;{
self = [super init];
if (self) {
_key = key;
_block = block;
}
return self;
}
- (void)dealloc {
NSLog(@"HZKVOInfo dealloc");
}
@end
自定义其他信息
static NSString *hzKVOSet = @"hzKVOSet";//自定义set方法 前缀
static NSString *kHZKVOAssociatedInfoArrayKey = @"kHZKVOAssociatedInfoArrayKey";//关联key
实现
void hz_KVOImp(id self,SEL _cmd,id newValue) {
//@"setKey:" -> @"key"
NSString *key = [NSStringFromSelector(_cmd) stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""];
key = [key stringByReplacingCharactersInRange:NSMakeRange(key.length - 1, 1) withString:@""];
NSString *keyStr = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[key substringWithRange:NSMakeRange(0, 1)] lowercaseString]];
//oldValue
id (*objc_msgSendGet)(id, SEL, id) = (void *)objc_msgSend;
id oldValue = objc_msgSendGet(self,NSSelectorFromString(keyStr),newValue);
//hzKVOKey SEL
SEL kvoSEL = NSSelectorFromString([NSString stringWithFormat:@"%@%@:",hzKVOSet,[keyStr capitalizedString]]);
//setKey: -> IMP
void (*objc_msgSendSet)(id, SEL, id) = (void *)objc_msgSend;
objc_msgSendSet(self,kvoSEL,newValue);
//关联数组
NSMutableArray *infoArr = objc_getAssociatedObject(self, (__bridge const void *)(kHZKVOAssociatedInfoArrayKey));
//遍历
for (HZKVOInfo *info in infoArr) {
if ([info.key isEqualToString:keyStr]) {
//Block
info.block(keyStr,oldValue,newValue);
}
}
}
-(void)hz_kvoAddObserverForKeyPath:(NSString *)keyPath block:(HZ_KVOBlock)block {
//@"_keyPath"
NSString *pathName = [NSString stringWithFormat:@"_%@",keyPath];
//setKeyPath SEL
Ivar keyIvar = class_getInstanceVariable([self class], [pathName UTF8String]);
SEL keySetter = NSSelectorFromString([NSString stringWithFormat:@"set%@:",[keyPath capitalizedString]]);
//容错
if (!keyIvar) { [self doesNotRecognizeSelector:keySetter];}
//关联 数组
NSMutableArray *infos = objc_getAssociatedObject(self, (__bridge const void *)kHZKVOAssociatedInfoArrayKey);
if (!infos) {
infos = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)kHZKVOAssociatedInfoArrayKey, infos, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// setKeyPath Method
Method setmethod = class_getInstanceMethod([self class], keySetter);
// hzKVOKeyPath SEL
SEL kvoSEL = NSSelectorFromString([NSString stringWithFormat:@"%@%@:",hzKVOSet,[keyPath capitalizedString]]);
if (!class_respondsToSelector([self class], kvoSEL)) {
if(class_addMethod([self class], kvoSEL, (IMP)hz_KVOImp, method_getTypeEncoding(setmethod))) {
//hzKVOKeyPath Method
Method kvoMehtod = class_getInstanceMethod([self class], kvoSEL);
//交换 IMP
method_exchangeImplementations(kvoMehtod, setmethod);
//添加 info
HZKVOInfo *info = [[HZKVOInfo alloc] initWithKey:keyPath block:block];
[infos addObject:info];
}
}
}
按着注释应该很好理解,流程:
- 通过名称查找相关key名称的set方法的method
- 根据key名称,在类添加相应的kvoSet方法,并取得method
- 交换俩个method的实现
- 在关联数组里添加信息
- 自定义实现hz_KVOImp
- 给属性赋值时 setKey(SEL) -> hzKVOSetKey(IMP) -> hzKVOSetKey(SEL) -> setKey(IMP)
- 调用block回调
测试
Honzon *h1 = [[Honzon alloc] init];
[h1 hz_kvoAddObserverForKeyPath:@"test" block:^(NSString *keyPath, id oldValue, id newValue) {
NSLog(@"key:%@ oldValue:%@ newValue:%@",keyPath, oldValue, newValue);
//-> key:test oldValue:(null) newValue:test
//-> key:test oldValue:test newValue:test two
}];
h1.test = @"test";
NSLog(@"%@",h1.test);
//-> test
h1.test = @"test two";
NSLog(@"%@",h1.test);
//-> test two
//Honzon dealloc
//HZKVOInfo dealloc
参考博客:如何自己动手实现 KVO