面试题:
- iOS用什么方式实现对一个对象的KVO?(KVO的本质)
- KVC的赋值和取值过程是怎样的?原理是什么?
一、KVO
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
通过一个小案例,回顾一下KVO的使用方法。示例程序:
1 | @interface DBPerson : NSObject |
1.1. 分析
疑问:self.person1.age = 11
和self.person2.age = 22
的本质都是调用age的setter方法,为什么能够监听到person1属性age的值变化,监听不到person2属性age的值变化?
person1和person2是不同的对象,我们看一下isa指针相关信息。
通过打印看到,person1的isa指向了NSKVONotifying_DBPerson
,person2的isa指向了DBPerson
。我们创建的person1是DBPerson
类,怎么变成了NSKVONotifying_DBPerson
?而且我们也没有去创建这个类。
当一个对象使用KVO后,程序就会动态为这个对象的类创建一个新类,类名是NSKVONotifying_类名
,并且新创建的类是对象之前类的子类。所以,NSKVONotifying_DBPerson
是程序运行期间使用runtime动态创建的DBPerson
的子类。
未使用KVO监听的对象,对象的isa指向DBPerson类对象。
使用了KVO监听的对象,对象的isa指向NSKVONotifying_DBPerson类对象,NSKVONotifying_DBPerson类对象的superclass指向DBPerson类对象。NSKVONotifying_DBPerson类对象实现了setAge:
方法,所以person1会执行NSKVONotifying_DBPerson类对象中的setAge:
方法。
从这一点也可以看出,对象的isa指向不同,最终的结果也可能会不一样。
NSKVONotifying_DBPerson类对象中的setAge:
方法内部调用了Foundation框架中的_NSSetIntValueAndNotify
函数(是一个C函数)。
NSKVONotifying_DBPerson的伪代码:
1 | @implementation NSKVONotifying_DBPerson |
注意:如果一个对象使用了KVO,不要手动创建名为
NSKVONotifying_对象isa指向类的类名
的类,否则KVO就会失效。
1.2. 验证
1.2.1. 验证NSKVONotifying_DBPerson
类的生成时机
runtime中的object_getClass()
方法可以获取对象isa指向的类名。
1 | NSLog(@"person1添加KVO[前]:%@", object_getClass(self.person1)); |
经过验证发现,NSKVONotifying_DBPerson
派生类是在对象添加KVO监听后动态生成的。
1.2.2. 验证_NSSetIntValueAndNotify
函数的存在
runtime中的methodForSelector()
方法可以获取指定方法名的实现地址。
1 | NSLog(@"person1添加KVO[前]:%p", [self.person1 methodForSelector:@selector(setAge:)]); |
通过lldb指令打印对应的方法实现地址可以发现,已添加KVO的对象调用属性的setter方法是在派生类的_NSSetIntValueAndNotify()
函数中实现的。
_NSSetIntValueAndNotify
仅仅是因为我们监听的属性是int类型而使用的函数,对应其他类型的有_NSSetBoolValueAndNotify
、_NSSetObjectValueAndNotify
、_NSSetRectValueAndNotify
等很多类型的函数。
1.2.3. 获取NSKVONotifying_DBPerson
类的对象方法
通过runtime的class_copyMethodList()
可以获取到一个类已实现的所有对象方法。
1 | // 打印指定类的所有实现方法 |
二、KVC
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。
常见的API有:
1 | // 赋值 |
2.1. setValue:forKey:的原理
+ (BOOL)accessInstanceVariablesDirectly {}
:是否允许直接访问成员变量,默认返回值是YES。
2.2. valueForKey:的原理
面试题1:iOS用什么方式实现对一个对象的KVO?(KVO的本质)
解答:利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify()
函数:
willChangeValueForKey:
- 父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法observeValueForKeyPath:ofObject:change:context:
面试题2:如何手动触发KVO?
解答:手动调用willChangeValueForKey:
和didChangeValueForKey:
(两个方法缺一不可)。
面试题3:直接修改成员变量会触发KVO么?
解答:不会触发KVO,因为不会触发setter方法。如果想要触发KVO,可以手动触发(面试题2)。
面试题4:通过KVC修改属性会触发KVO么?
解答:会触发KVO。如果没有属性只有成员变量,在setValue:forKey:
内部会调用willChangeValueForKey:
和didChangeValueForKey:
,所以会触发KVO。
面试题5:KVC的赋值和取值过程是怎样的?原理是什么?
解答:参考2.1和2.2的图。