KVO原理

作者: SPIREJ | 来源:发表于2019-11-12 22:49 被阅读0次

探索KVO原理

有这么一个示例:

  1. Person类,有一个实例变量nickName,属性name,对象方法run,work:
// Person.h 
@interface Person : NSObject
{
    NSString *nickName;
}

@property (nonatomic, copy) NSString *name;

- (void)run;

- (void)work;

@end

// Person.m 实现方法
- (void)work {
}

- (void)run {
}
  1. Student类继承自Person类:
// Student.h 什么也没做
@interface Student : Person

@end

// Student.m 实现继承来的run方法
- (void)run {    
}
  1. 有两个工具方法,遍历类的方法 和 遍历类及子类
#pragma mark - 遍历方法

- (void)printClassAllMethod:(Class)cls {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableArray *mArray = [NSMutableArray array];
    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);
        [mArray addObject:NSStringFromSelector(sel)];
    }
    NSLog(@"allMethod = %@", mArray);
}

#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);
}
  1. 调用工具方法打印Person 和 Student的所有方法及类和子类
[self printClassAllMethod:[Person class]];
[self printClassAllMethod:[Student class]];

[self printClasses:[Person class]];
[self printClasses:[Student class]];
  1. 打印结果
Person -- ClassAllMethod
2019-11-12 22:07:27.189171+0800 KVO[29094:3896664] allMethod = (
    ".cxx_destruct",
    name,
    "setName:",
    run,
    work
)

Student -- ClassAllMethod
2019-11-12 22:07:27.189791+0800 KVO[29094:3896664] allMethod = (
    run
)

Person -- Classes
2019-11-12 22:07:27.217213+0800 KVO[29094:3896664] classes = (
    Person,
    Student
)

Student -- Classes
2019-11-12 22:07:27.222664+0800 KVO[29094:3896664] classes = (
    Student
)
  • Persor类的所有方法,可以看到除了声明的run,work,还有系统自动生成的属性的setter和getter方法,实例变量nickName没有生成setter和getter方法
  • Student类只有继承来的run,且方法实现地址imp和父类不一样
  • Person类除了自身还有子类Student
  • Student只有自己
    》》》和我们预期的完全一样《《《
  1. 添加一个观察者
self.person = [[Person alloc] init];
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:nil];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];

[self printClasses:[Person class]];

打印发现此时的Person类及子类,多了一个NSKVONotifying_Person类,说明NSKVONotifying_Person是动态生成的Person的子类

2019-11-12 22:28:32.530026+0800 KVO[29303:3917560] classes = (
    Person,
    "NSKVONotifying_Person",
    Student
)

我们断点观察者注册前后,发现实例对象self.person真正指向的类从Person变为了NSKVONotifying_Person,真是神奇。

  1. 既然有NSKVONotifying_Person这个类,那么把这个类的所有方法打印出来瞧一瞧
[self printClassAllMethod:NSClassFromString(@"NSKVONotifying_Person")];

打印结果:

2019-11-12 22:39:27.756694+0800 KVO[29389:3928692] setName: -- 0x7fff2564cec6
2019-11-12 22:39:27.756979+0800 KVO[29389:3928692] class -- 0x7fff2564b989
2019-11-12 22:39:27.757287+0800 KVO[29389:3928692] dealloc -- 0x7fff2564b6ee
2019-11-12 22:39:27.757561+0800 KVO[29389:3928692] _isKVOA -- 0x7fff2564b6e6
2019-11-12 22:39:27.757720+0800 KVO[29389:3928692] allMethod = (
    "setName:",
    class,
    dealloc,
    "_isKVOA"
)

观察前
setName: -- 0x1038bf7d0
观察后
setName: -- 0x7fff2564cec6

哇,看到重点了,除了这是KVO类的标记和dealloc方法,看到了setName方法,再结合观察前后setName方法对应的方法实现IMP地址的不同,可以推断NSKVONotifying_Person重写了setter方法,因而在对象上对setter的调用就会调用已重写的setter,从而激活键值通知机制。

总结KVO原理:

  1. Apple使用了isa混写(isa-swizzling)来实现KVO。当观察对象为类A的属性时,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类继承自A的本类,且KVONSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用原setter方法之前和之后,通知所有观察对象属性值的更改情况。

  2. NSKVONotifying_A类剖析:在这个过程,被观察对象的isa指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类NSKVONotifying_A类,来实现当前类属性值改变的监听。

  3. 所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类,就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。

  4. isa指针的作用:每个对象都有isa指针,指向该对象的类,它告诉runtime系统这个对象的类时什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。因而在该对象上对setter的调用就会调用已重写的setter,从而激活键值通知机制。

  5. 子类setter方法剖析:KVO的键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:didChangeValueForKey:,在存取数值的前后分别调用2个方法:被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该keyPath的属性值即将变更;在改变发生之后,didChangeValueForKey:被调用,通知系统该keyPath的属性值已经变更;之后,observeValueForKey:ofObject:change:context:也会被调用。且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的。

示例代码可以在这里下载:
https://github.com/SPIREJ/KVO

相关文章

网友评论

      本文标题:KVO原理

      本文链接:https://www.haomeiwen.com/subject/ocqcictx.html