KVO+KVC底层原理
2020-08-03 本文已影响0人
鼬殿
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
未使用KVO监听的对象
KVO的实现
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Person
@end
@interface ViewController ()
@property (strong, nonatomic) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc] init];
self.person.age = 10;
// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.age = 20;
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
KVO的底层实现
在上面的代码基础上添加验证打印
#import <objc/runtime.h>
NSLog(@"person添加KVO监听之前,self.person实例变量的isa指针指向 - %@类对象",object_getClass(self.person));
// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person添加KVO监听之后setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
person添加KVO监听之前,self.person实例变量的isa指针指向 - Person类对象
person添加KVO监听之后,self.person实例变量的isa指针指向- NSKVONotifying_Person类对象
- 可以看到KVO是利用Runtime的API动态生成一个子类,并且让instance对象的isa指向这个全新的子类
NSLog(@"person添加KVO监听之前setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person添加KVO监听之后setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
person添加KVO监听之前setAge方法内存地址 - 0x102105cdc
person添加KVO监听之后setAge方法内存地址 - 0x18b412834
(lldb) p (IMP)0x102105cdc
(IMP) $0 = 0x0000000102105cdc (Interview01`-[Person setAge:] at ViewController.m:13)
(lldb) p (IMP)0x18b412834
(IMP) $1 = 0x000000018b412834 (Foundation`_NSSetIntValueAndNotify)
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
#import <objc/runtime.h>
[self printMethodNamesOfClass:object_getClass(self.person)];
// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
[self printMethodNamesOfClass:object_getClass(self.person)];
- (void)printMethodNamesOfClass:(Class)cls
{
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[I];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
Person age, setAge:
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA
可以看到NSKVONotifying_Person中实现了上述方法,由以上结论我们可以得出KVO底层实现的伪代码大致如下:
@interface NSKVONotifying_Person : Person
@end
@implementation NSKVONotifying_Person
- (void)setAge:(int)age
{
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
// 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class
{
return [Person class];
}
- (void)dealloc
{
// 收尾工作
}
- (BOOL)_isKVOA
{
return YES;
}
@end
图册如下:
willChangeValueForKey:和didChangeValueForKey的调用我们可以在Person类验证
面试题
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
直接修改成员变量会触发KVO么?
不会触发KVO(没有触发set方法)
KVC
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
常见的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
setValue:forKey:的原理
accessInstanceVariablesDirectly方法的默认返回值是YES
@interface Person : NSObject
{
@public
int age;
int isAge;
int _isAge;
int _age;
}
@end
@implementation Person
- (void)setAge:(int)age
{
NSLog(@"setAge: - %d", age);
}
- (void)_setAge:(int)age
{
NSLog(@"_setAge: - %d", age);
}
// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
return YES;
}
@end
Person *person = [[MJPerson alloc] init];
[person setValue:@10 forKey:@"age"];
valueForKey:的原理
@interface Person : NSObject
{
@public
int age;
int isAge;
int _isAge;
int _age;
}
@end
@implementation Person
- (int)getAge
{
return 11;
}
- (int)age
{
return 12;
}
- (int)isAge
{
return 13;
}
- (int)_age
{
return 14;
}
// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
return YES;
}
@end
Person *person = [[Person alloc] init];
person->_age = 10;
person->_isAge = 11;
person->age = 12;
person->isAge = 13;
NSLog(@"%@", [person valueForKey:@"age"]);
面试题
通过KVC修改属性会触发KVO么?
会触发KVO,setValue:forKey:方法内部会调用
willChangeValueForKey:
didChangeValueForKey:
- (void)willChangeValueForKey:(NSString *)key
{
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key
{
[super didChangeValueForKey:key];
}