自定义的 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 判断来区别不同属性,应用简略。

注意事项

  1. 暂不反对上面的监听形式:
[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