拜伦的博客

Coder Byron

这里有关于iOS,机器学习的笔记心得,欢迎交流


iOS KVO详解

目录

提出疑问

首先抛出问题,KVO的原理有很多文章写过,大致原理是一个Test类,添加了观察者后,会动态生成NSKVONotifying_Test类,重写了set方法。但为什么当我们对这个对象调用class方法的时候依然返回的是Test类,为什么不是NSKVONotifying_Test? 我们能理解设计者是故意屏蔽内部实现,但是究竟是怎么做到的呢?

同样问题,superclass方法,获取的应该是Test和NSObject,为什么是2个都是NSObject?

Test *t = [Test new];
NSLog(@"before  class=%@,  object_getClass=%@,  super class=%@",t.class,object_getClass(t),t.superclass);
[t addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];    
NSLog(@"after  class=%@,  object_getClass=%@,  super class=%@",t.class,object_getClass(t),t.superclass);
before  class=Test,  object_getClass=Test,  super class=NSObject
after  class=Test,  object_getClass=NSKVONotifying_Test,  super class=NSObject

如上这样生成一个类,打印添加观察者前后调用class得到的结果,发现没变,可是控制台中或者理论上这个对象指向的都应该是NSKVONotifying_Test,但是object_getClass拿到的却是我们想要的,下面从class和object_getClass方法说起。

截屏2020-12-28 下午5.42.01

class方法和object_getClass方法

class和superclass方法的实现如下

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

- (Class)superclass {
    return [self class]->superclass;
}

object_getClass方法实现如下

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

正常情况下,一个实例对象调用class方法和调用object_getClass的结果肯定一致,因为class的实现就是通过object_getClass。 所以这里NSKVONotifying_Test这个类肯定也重写了class方法,那么如何去证明呢?

证明

Test *t = [Test new];

IMP p1 = class_getMethodImplementation(t.class, @selector(class));
IMP p3 = class_getMethodImplementation(t.class, @selector(name));
IMP p5 = class_getMethodImplementation(t.class, @selector(setName:));
IMP p7 = class_getMethodImplementation(t.class, @selector(superclass));
NSLog(@"befor class=%p,   getName=%p,   setName=%p,   superclass=%p", p1,p3, p5,p7);

[t addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        
IMP p2 = class_getMethodImplementation(object_getClass(t), @selector(class));
IMP p4 = class_getMethodImplementation(object_getClass(t), @selector(name));
IMP p6 = class_getMethodImplementation(object_getClass(t), @selector(setName:));
IMP p8 = class_getMethodImplementation(object_getClass(t), @selector(superclass));
NSLog(@"after class=%p,   getName=%p,   setName=%p,  superclass=%p", p2,p4, p6, p8);

这里证明思路是,如果NSKVONotifying_Test重写了class方法,那么IMP指针指向的实现地址肯定会改变,如果添加观察者前后地址改变了,则证明重写了该方法。

结果如下

befor class=0x7fff2018ec9b,   getName=0x10a970910,   setName=0x10a970940,   superclass=0x7fff2018ecde
after class=0x7fff207b4662,   getName=0x10a970910,   setName=0x7fff207b5b57,  superclass=0x7fff2018ecde

这能证明,class和setName方法被重写,get方法和superclass方法没重写。class的实现可能如下

- (Class)class {
    return objc_getClass("Test");
}

这样就能解释为什么添加观察者之后的对象调用class拿到的依然是Test类,系统为了屏蔽内部实现,故重写了OC层面的class方法,但是依然给了我们一个runtime层面object_getClass的入口。

并且因为superclass的实现是调用[self class]方法,调用的是重写后的,拿到的class是Test,所以可以理解了superclass也就不需要重写了。

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦