自定义的 KVO,反对多属性监听,反对主动开释。
应用零碎 KVO 监听属性
@property (nonatomic, copy) NSString *msg;@property (nonatomic, strong) Person *person;
1、增加观察者
[self addObserver:self forKeyPath:@"msg" options:NSKeyValueObservingOptionNew context:nil];[self addObserver:self forKeyPath:@"person.name" options:NSKeyValueObservingOptionNew context:nil];
2、解决回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"msg"]) { NSLog(@"msg 新值 : %@", self.msg); } if ([keyPath isEqualToString:@"person.name"]) { NSLog(@"person.name 新值 : %@", self.person.name); }}
3、手动移除观察者
[self removeObserver:self forKeyPath:@"msg"];[self removeObserver:self forKeyPath:@"person.name"];
零碎 KVO 须要写一大堆代码,须要手动开释,须要咱们本人判断监听是哪个属性,咱们通过自定义 KVO 主动解决这些流程。
应用自定义 EasyKVO 监听属性
监听 msg 属性
[self observeProperty:@"msg" changedBlock:^(id newValue, id oldValue) { NSLog(@" > msg : 旧值 %@, 新值 %@", oldValue, newValue);}];
监听 person.name 属性
[self.person observeProperty:@"name" changedBlock:^(id newValue, id oldValue) { NSLog(@" > person.name : 旧值 %@, 新值 %@", oldValue, newValue);}];
相比于零碎的 kvo,自定义 kvo 不须要手动开释,不须要再回调函数中增加很多 if 判断来区别不同属性,应用简略。
注意事项
- 暂不反对上面的监听形式:
[self observeProperty:@"person.name" changedBlock:^(id newValue, id oldValue) { NSLog(@" > person.name : 旧值 %@, 新值 %@", oldValue, newValue);}];
2.请不要和零碎 KVO 一起应用, 因为 isa 指针的关系, 会造成抵触.
次要原理简介
1、创立自定义派生类
NSString *oldClassName = NSStringFromClass([self class]);NSString *pairClassName = [EASY_KVO_PREFIX stringByAppendingString:oldClassName];Class pairClass = NSClassFromString(pairClassName);pairClass = objc_allocateClassPair([self class], pairClassName.UTF8String, 0x68);objc_registerClassPair(pairClass);
2、增加主动开释的办法
if (!_containSelector(self, @"dealloc")) { SEL deallocSEL = NSSelectorFromString(@"dealloc"); Method deallocMethod = class_getInstanceMethod([self class], deallocSEL); const char * deallocType = method_getTypeEncoding(deallocMethod); class_addMethod(pairClass, deallocSEL, (IMP)easy_dealloc, deallocType);} else { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc")); Method m2 = class_getInstanceMethod([self class], @selector(selEasyDealloc)); method_exchangeImplementations(m1, m2); });}
dealloc实现
- (void)selEasyDealloc { __easy_dealloc(self); [self selEasyDealloc];}void easy_dealloc(id self, SEL _cmd) { __easy_dealloc(self);}void __easy_dealloc(id self) { NSLog(@"-- easy_dealloc %@", self); NSMutableDictionary *tips = _globalTipsMap(); [tips removeAllObjects]; Class oldClass = [self class]; object_setClass(self, oldClass); /* 改回 isa 指针 */ /* 开释所有属性和变量 */ unsigned int count = 0; Ivar * ivarList = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivarList[i]; const char * name = ivar_getName(ivar); NSString * KEY = [NSString stringWithUTF8String:name]; [self setValue:nil forKey:KEY]; //NSLog(@"prop %@ released", KEY); } }
3、重写 setter 办法
SEL setSel = NSSelectorFromString(_setterForProperty(keyPath));Method setMethod = class_getInstanceMethod([self class], setSel);const char * setType = method_getTypeEncoding(setMethod);class_addMethod(pairClass, setSel, (IMP)easy_setter, setType);
setter 实现,并在此回调 block
void easy_setter(id self, SEL _cmd, id newValue) { NSString *setterName = NSStringFromSelector(_cmd); if (setterName.length < 4) return; NSString *format = [setterName substringWithRange:NSMakeRange(3, setterName.length - 4)]; NSString *keyPath = [format stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[format substringToIndex:1] lowercaseString]]; if (keyPath.length < 1) return; id oldValue = [self valueForKeyPath:keyPath]; if (![oldValue isEqual:newValue]) { //调用父类setter struct objc_super superClass = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; void (* msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper; msgSendSuper(&superClass, _cmd, newValue); } // 回调 block NSString *KEY = [NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(object_getClass(self)), keyPath]; NSMutableDictionary * tipMap = _globalTipsMap(); EasyKVOChangedBlock block = (EasyKVOChangedBlock)[tipMap objectForKey:KEY]; if (block) { block(newValue, oldValue); }}
4、批改 isa 指针
object_setClass(self, pairClass);
5、保留 block 回调信息
NSString * KEY = [NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(pairClass), keyPath];NSMutableDictionary *tips = _globalTipsMap();if (block) { [tips setObject:[block copy] forKey:KEY];} else { [tips setObject:[^{} copy] forKey:KEY];}
Demo
demo地址:
https://github.com/jerodji/EasyKVO