iOS窥探KVO底层实现原理篇

最近小编公司招聘 iOS, 于是小编从网上找了几道面试题,来考察候选人iOS 开发方面的技术水平,其中有一道面试题便是 KVO 底层实现是什么? 如何手动出发 KVO? 修改成员变量的值会出发 KVO 吗? KVC 赋值会出发 KVO 吗? 当你了解 KVO 实现原理后,这几道面试题自然不在话下.接下来我将通过代码和讲解来窥探 KVO 背后的奥秘.

首先创建一个 Person 类 内部有个 name 属性,然后 创建p1 和 p2两个实例对象,其中p1添加了kvo监听,p2没有添加 kvo 监听,然后重写了 observeValueForKeyPath 方法 监听Person.name 属性发生改变时候的通知.

从本质上来看 Person 给name赋值的时候 调用的是 setName 方法 ,无论 p1还是p2 调用的 setter 方法都是一样的,为什么 p1改变 name 属性值就能有通知, p2确没有,调用的 都是同一个 setName:(NSString *)name 方法,区别怎么那么大?

小编窥探尝试1

接下来小编打印下p1和p2的内存地址 看看p1和p2内存地址能不能一探究竟.

从 p1和 p2内存地址上也看不出来什么东东.

小编窥探尝试2 打印 p1和 p2 的 class 信息

what 什么 输出的 class 都是 Person 类 ,既然同一个类 同一个 setter 方法,为什么我们不一样呢?

小编窥探尝试3 打印 object_getClass 试试看 我们都知道object_getClass(id) 才会返回这个实例对象的真实 class 类型

什么 , 添加 KVO 之后说好的 Person 类跑哪去了, NSKVONotifying_Person是什么东东?

为了进一步窥探 KVO 添加前后的变化 小编窥探尝试4 打印 setName 方法实现IMP指针有没有发生改变,我们知道同一个方法的实现 IMP 地址是不变的.

连 setName方法都不一样了 , 为了一探究竟 小编绝对对上边的 NSKVONotifying_Person 和 添加 KVO 之后的 imp 指针进行进一步研究.

首先 在 lldb 上输入 imp1和 imp2

发生了 imp1 方法实现在 Foundation 框架里的 _NSSetObjectValueAndNotify 函数中 ,而 imp2 则调用了 Person setName 方法

也就是说添加了 KVO 之后 p1 修改 name 值之后 不再调用 Person 的 setName方法 ,而 p2没有添加 kvo 监听 依然正常调用 setName:方法 ,由此可以得出 p1 添加完 KVO 监听后 系统修改了默认方法实现,那么既然没有调用 setName: 方法 为什么 p1.name 的值也发生了改变?

接下来我们准备对刚才 NSKVONotifying_Person 类进行下一步研究, NSKVONotifying_Person 和 Person 有没有内在的联系呢?

小编窥探尝试5 NSKVONotifying_Person和 Person 之间的联系时什么

通过打印 NSKVONotifying_Person 的 superclass 和 Person 的 superclass 可以得出, NSKVONotifying_Person是一个 Person 子类,那么为什么苹果会动态创建这么一个 子类呢? NSKVONotifying_Person 这个子类 跟 Person 内部有哪些不同呢 ?

这个时候 我们去输出下 Person 和 NSKVONotifying_Person 内部的方法列表 和 属性列表 ,看看NSKVONotifying_Person 子类都添加了那些方法和属性.


- (void)viewDidLoad {
	[super viewDidLoad];

	
	Person *p1 = [[Person alloc] init];
	Person *p2 = [[Person alloc] init];
	
	id cls1 = object_getClass(p1);
	id cls2 = object_getClass(p2);
	NSLog(@"添加 KVO 之前: cls1 = %@  cls2 = %@ ",cls1,cls2);
	
	[p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
	 cls1 = object_getClass(p1);
	 cls2 = object_getClass(p2);
	
	
	NSString *methodList1 = [self printPersonMethods:cls1];
	NSString *methodList2 = [self printPersonMethods:cls2];

	NSLog(@"%@",methodList1);
	NSLog(@"%@",methodList2);

	
//	NSLog(@"添加 KVO 之后: cls1 = %@  cls2 = %@ ",cls1,cls2);
	
//	id super_cls1 = class_getSuperclass(cls1);
//	id super_cls2 = class_getSuperclass(cls2);
//
//	NSLog(@"super_cls1 = %@ ,super_cls2 = %@",super_cls1,super_cls2);
//
//	p1.name = @"dzb";
//	p2.name = @"123";

}

- (NSString *) printPersonMethods:(id)obj {
	
	unsigned int count = 0;
	Method *methods = class_copyMethodList([obj class],&count);
	NSMutableString *methodList = [NSMutableString string];
	[methodList appendString:@"[\n"];
	for (int i = 0; i<count; i++) {
		Method method = methods[i];
		SEL sel = method_getName(method);
		[methodList appendFormat:@"%@",NSStringFromSelector(sel)];
		[methodList appendString:@"\n"];
	}
	
	[methodList appendFormat:@"]"];
	
	free(methods);
	
	return methodList;
}

复制代码

从输出结果可以看出来 NSKVONotifying_Person 内部也有一个 setName:方法 还重写了 class 和 dealloc 方法 , _isKVOA, 那么我们可以大致的得出, p1添加 kVO 后 runtime 动态的生成了一个 NSKVONotifying_Person子类 并重写了 setName 方法 ,那么 setName 内部一定是做了一些事情,才会触发 observeValueForKeyPath 监听方法.

继续探究 NSKVONotifying_Person 子类 重写 setName 都做了什么? 其实 setName 方法内部 是调用了 Foundation 的 _NSSetObjectValueAndNotify 函数 ,在 _NSSetObjectValueAndNotify 内部

1首先会调用 willChangeValueForKey
2然后给 name 属性赋值 3 最后调用 didChangeValueForKey 4最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变 .

由于苹果 Foundation 框架是不开源的 ,所以我们依然可以通过重写Person 的 willChangeValueForKey 和 didChangeValueForKey 验证我们的猜想 .

首先当我们改变p1.name 的值时 并不是首先执行的 setName: 这个方法 ,而是先调用了 willChangeValueForKey 其次 调用父类的 setter 方法 对属性赋值 ,然后再调用 didChangeValueForKey 方法 ,并在 didChangeValueForKey 内部 调用监听器的 observeValueForKeyPath方法 告诉外界 属性值发生了改变.

至于重写了 dealloc 和 class 方法 是为了做一些 KVO 释放内存 和 隐藏外界对于 NSKVONotifying_Person 子类的存在

这就是我们调用 [p1 class] 和 [p2 class]结果都显示 Person 类 ,让我们误以为 Person 没有发生变化 补充说明 ,KVC 对属性赋值时候 是会在这个类里边 去查找 _age isAge setAge setIsAge 等方法的 ,最终会调用属性的 setter 方法 ,那么如果添加了 KVO 还是会被触发的 . 相反 设置成员变量 _age 由于不会触发 setter 方法 ,因此不会去触发 KVO 相关的代码 .

好了,我是大兵布莱恩特,欢迎加入博主技术交流群,iOS 开发交流群

转载于:https://juejin.im/post/5b46e5955188251abd7d1676

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值