KVO的使用与原理探索

184 阅读2分钟

1.KVO介绍

KVO键值观察``key value observing的简称,在iOS开发中,可使用KVO来监听一个对象属性的变化。

2.KVO的使用

2.1.对某个类的属性添加监听

[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
  • self.personLGPerson的实例对象;
  • LGPerson的实例对象self.person添加观察者self,监听self.person的属性nick新值变化(NSKeyValueObservingOptionNew).

2.2.观察者的回调方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
  • 当被观察者的属性变化时,会来到这个回调方法,keyPath是被监听的属性,object是被观察的对象,change是数据的变化。

2.3.移除观察者

- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"nick" context:NULL];
   
}

移除selfself.person对属性nick的监听; 必须要移除,不然会崩溃。

2.4.数组类型的属性

self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];

对象的属性--数组,需要使用KVC的形式赋值才能监听

关闭KVO

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
}

当上面的方法返回NO时,整个类的监听都会被关闭。

也可以只关闭某个属性的监听,这里以nick属性为例

+(BOOL)automaticallyNotifiesObserversOfNick
{
    return NO;
}

3.KVO的底层原理

3.1.添加监听时的处理

实现如下方法,打印类及其子类

#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
    
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    Class* classes = (Class*)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i<count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [mArray addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
}

实现如下方法打印类的方法列表

#pragma mark - 遍历类的方法列表
- (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}

通过监听前后对LGPerson及其子类列表的打印,我们发现,当对LGPerson的对象self.person进行监听后,系统自动生成了一个LGPerson的子类NSKVONotifying_LGPerson

再来看看NSKVONotifying_LGPerson的方法列表

可以看到,NSKVONotifying_LGPerson中重写了父类的setNickName:方法

再来看self.personisa指向

可以看到self.personisa指向,由LGPerson变成了NSKVONotifying_LGPerson.

通过上面的分析我们可以得出:当对一个对象的属性进行监听时,会生成一个当前对象所属类的子类NSKVONotifing_ClassName,并将当前对象的isa指针指向新创建的这个派生类。在这个派生类中重写了当前对象所监听的属性的setter方法。在这个派生类中还重写了class方法,当调用[self.person class]时,返回的是LGPerson,还实现了一个isKVO方法,标记当前类是实现监听的派生类.

3.2.销毁监听时的处理

  • 可以看到,销毁监听后,派生类NSKVONotifying_LGPerson依然存在,这样设计可以方便下次监听时不用重复创建了;
  • 销毁监听后对象的isa指针指回了LGPerson;

当对一个对象属性的监听被移除后,对象的isa指针会重新指向原来的类,保证了不会出现混乱,这也体现了结束监听时销毁监听的重要性。