object-c 利器之KVO深究(一)-- KVO实现原理

iOS开发中,监听对象某一属性变化而做出响应的模式十分常用,俗称,观察者模式。

具体用法十分简单,我们只需给想要监听的对象添加一个观察者就可以实现灵动的操控。如:

@interface NSObject(NSKeyValueObserverRegistration)

/* Register or deregister as an observer of the value at a key path relative to the receiver. The options determine what is included in observer notifications and when they're sent, as described above, and the context is passed in observer notifications as described above. You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong.
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

需要注意的是,传入的observer不能为空,keypath要对应,并且如果不再支持监听时(包括不限于observer释放)移除observer。

用法很简单,但是Apple是如何实现不需要给原对象添加额外代码就可以监听呢?此处,做一个小猜测:Apple拦截了一些消息,在监听到某些属性改变时,Apple就发送通知,告知观察者。

我们先从根源猜起,要想做到属性值一改变就发送通知,如果我们去实现,就得在setter里动手脚。既然对原有类或者对象没有修改,那么怎么搞?继承!我们继承原来的类,重写setter,这样就可以拦截setter消息,实现我们的需求。但是并没有看见苹果继承的动作,并且-class返回还是原有类,这是为什么?那就只有一个可能,就是动态生成类!

NO栗子,SAY JB!

写一个类,只有简单的几个属性:

@interface TestClass : NSObject
@property int x;
@property int y;
@property int z;
@end

@implementation TestClass
@end

在main.c文件中,定义两个函数:

static NSArray * ClassMethodNames(Class class){
    NSMutableArray *array = [NSMutableArray array];
    unsigned int outCount = 0;
    Method *methodList = class_copyMethodList(class, &outCount);
    unsigned int i;
    for (i = 0; i < outCount; i ++) {
        [array addObject:NSStringFromSelector(method_getName(methodList[i]))];
    }

    return array;
}

static void PrintDescription(NSString *name, NSObject *obj){
    NSString *str = [NSString stringWithFormat:
                     @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                     name,
                     obj,
                     class_getName([obj class]),
                     class_getName(object_getClass(obj)),
                     [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
    printf("%s\n", [str UTF8String]);
}

使用 class_copyMethodList可以获取到本类的代码列表,不包括父类实现的。

然后,上main.c:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        TestClass *x = [[TestClass alloc] init];
        TestClass *y = [[TestClass alloc] init];
        TestClass *xy = [[TestClass alloc] init];
        TestClass *control = [[TestClass alloc] init];

        [x addObserver:x forKeyPath:@"x" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
        [x addObserver:x forKeyPath:@"z" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
        [xy addObserver:xy forKeyPath:@"x" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
        [y addObserver:y forKeyPath:@"y" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
        [xy addObserver:xy forKeyPath:@"y" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];

        PrintDescription(@"control", control);
        PrintDescription(@"x", x);
        PrintDescription(@"y", y);
        PrintDescription(@"xy", xy);

        printf("\n\tUsing NSObject methods, normal setX: is %p, overridden setX: is %p\n",
               [control methodForSelector:@selector(setX:)],
               [x methodForSelector:@selector(setX:)]);
        printf("\tUsing libobjc functions, normal setX: is %p, overridden setX: is %p\n",
               method_getImplementation(class_getInstanceMethod(object_getClass(control),
                                                                @selector(setX:))),
               method_getImplementation(class_getInstanceMethod(object_getClass(x),
                                                                @selector(setX:))));

        [x removeObserver:x forKeyPath:@"x"];
        [x removeObserver:x forKeyPath:@"z"];
        [xy removeObserver:xy forKeyPath:@"x"];
        [y removeObserver:y forKeyPath:@"y"];
        [xy removeObserver:xy forKeyPath:@"y"];
     }
    return 0;
}

输出:

control: <TestClass: 0x100206f00>
    NSObject class TestClass
    libobjc class TestClass
    implements methods <x, y, z, setX:, setY:, setZ:>
x: <TestClass: 0x100206e60>
    NSObject class TestClass
    libobjc class NSKVONotifying_TestClass
    implements methods <setY:, setZ:, setX:, class, dealloc, _isKVOA>
y: <TestClass: 0x100206ec0>
    NSObject class TestClass
    libobjc class NSKVONotifying_TestClass
    implements methods <setY:, setZ:, setX:, class, dealloc, _isKVOA>
xy: <TestClass: 0x100206ee0>
    NSObject class TestClass
    libobjc class NSKVONotifying_TestClass
    implements methods <setY:, setZ:, setX:, class, dealloc, _isKVOA>

    Using NSObject methods, normal setX: is 0x100001b70, overridden setX: is 0x7fff8cb0aba9
    Using libobjc functions, normal setX: is 0x100001b70, overridden setX: is 0x7fff8cb0aba9
Program ended with exit code: 0

从log可以看到:没有添加observer的对象(control)仍归属于原有类(TestClass),但是添加observer的对象(x,y,z)都归属了另一个莫名其妙的类—NSKVONotifying_TestClass,该类就是动态生成的继承自TestClass的子类,且这些类除了重写setter之外还另外重写了class,dealloc,_isKVOA。重写class的目的是Apple为了隐藏KVO的实现细节,制造一种假象。

至此,我们的探索就算结束了,得到的结论也印证了之前的猜想(其实也是知道结论后,故弄玄虚)。

总结:
监听一个对象时,runtime会动态生成一个继承自该类的子类,目的是为了重写setter等方法,这样就可以满足值改变时发送通知的需求。

下一篇,会聊聊手动实现KVO。

参考:
https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值