细说OC中的KVO
Posted wangjunling888
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说OC中的KVO相关的知识,希望对你有一定的参考价值。
KVO这个特异功能相信很多人都应该熟知, 就算工作时没有用到, 那么面试的时候肯定被面试官提到过, 虽然算不上黑魔法, 但是了解一下实现原理, 对我们还是有很大帮助的, 下面笔者将一步一步深挖KVO的实现原理!
创建一个Person类, 添加一个属性name, 当然你可以添加任何你想要的属性, 这里笔者就用name来做演示使用
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
//一下为实现文件, 代码内容暂时不要纠结, 接下来会细说
//控制器中使用KVO
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
[self test];
- (void)test
Person *person = [[Person alloc] init];
person.name = @"lisi";
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
person.name = @"zhangsan";
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
if ([keyPath isEqualToString:@"name"])
NSLog(@"%@", change);
以上代码运行结果如下
2018-09-05 09:06:04.437498+0800 KVODemo[29221:23851046]
kind = 1;
new = zhangsan;
old = lisi;
因为是在赋值的时候才能获取到通知, 所以首先应该想到, setter方法, 于是笔者给Setter方法加入log
运行结果如下
2018-09-05 09:19:01.981135+0800 KVODemo[29406:23866065] lisi
2018-09-05 09:19:01.981448+0800 KVODemo[29406:23866065] zhangsan
2018-09-05 09:19:01.981610+0800 KVODemo[29406:23866065]
kind = 1;
new = zhangsan;
old = lisi;
我们看到在第二次赋值时, 发出了通知, 那么是否是因为Setter方法不一样而导致的呢, 于是笔者加入了打印Setter方法地址的代码如下
- (void)test
Person *person = [[Person alloc] init];
person.name = @"lisi";
SEL sel = @selector(setName:);
IMP imp = [person methodForSelector:sel];
NSLog(@"添加KVO之前的setter地址%p", imp);
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
person.name = @"zhangsan";
sel = @selector(setName:);
imp = [person methodForSelector:sel];
NSLog(@"添加KVO之后的setter地址%p", imp);
运行结果如下,
2018-09-05 09:26:02.811444+0800 KVODemo[29515:23878046] lisi
2018-09-05 09:26:02.811580+0800 KVODemo[29515:23878046] 添加KVO之前的setter地址0x1036dc570
2018-09-05 09:26:02.811835+0800 KVODemo[29515:23878046] zhangsan
2018-09-05 09:26:02.811994+0800 KVODemo[29515:23878046]
kind = 1;
new = zhangsan;
old = lisi;
2018-09-05 09:26:02.812079+0800 KVODemo[29515:23878046] 添加KVO之后的setter地址0x103a359fa
观察结果会发现两次的地址是不一样的, 那么可以确定第二次调用的setName: 方法肯定是变了, 但是明明是同一个对象的同一个方法, 为什么不一样呢, 所以我们要看看两个不同的时间节点两个对象的真是类型, 然后加入以下代码
- (void)test
Person *person = [[Person alloc] init];
person.name = @"lisi";
SEL sel = @selector(setName:);
IMP imp = [person methodForSelector:sel];
Class clazz = object_getClass(person);
Class supClazz = class_getSuperclass(clazz);
NSLog(@"添加KVO之前的类: %@, 父类:%@", clazz, supClazz);
NSLog(@"添加KVO之前的setter地址%p", imp);
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
person.name = @"zhangsan";
clazz = object_getClass(person);
supClazz = class_getSuperclass(clazz);
NSLog(@"添加KVO之后的类名: %@, 父类:%@", clazz, supClazz);
sel = @selector(setName:);
imp = [person methodForSelector:sel];
NSLog(@"添加KVO之后的setter地址%p", imp);
通过运行时方法 object_getClass() 获取运行时当前对象的类型, 并通过class_getSuperclass() 获取父类, 在加入观察者之前, 和之后分别调用以下, 得到以下结果
2018-09-05 10:49:34.127586+0800 KVODemo[30673:23964294] lisi
2018-09-05 10:49:34.127717+0800 KVODemo[30673:23964294] 添加KVO之前的类: Person, 父类:NSObject
2018-09-05 10:49:34.127802+0800 KVODemo[30673:23964294] 添加KVO之前的setter地址0x105bf7500
2018-09-05 10:49:34.128079+0800 KVODemo[30673:23964294] zhangsan
2018-09-05 10:49:34.128283+0800 KVODemo[30673:23964294]
kind = 1;
new = zhangsan;
old = lisi;
2018-09-05 10:49:34.128388+0800 KVODemo[30673:23964294] 添加KVO之后的类名: NSKVONotifying_Person, 父类:Person
2018-09-05 10:49:34.128468+0800 KVODemo[30673:23964294] 添加KVO之后的setter地址0x105f509fa
从运行结果中可以可以看到, 加入观察者之后, person的实际类型已经变了, 此时的person实际类型为NSKVONotifying_Person, 继承自Person, 到此不难得出一个结论, 即苹果在代码运行时创建了继承自Person类的NSKVONotifying_Person类用来实现对Person类属性的的监测的, 那么他是如何实现的呢? 接下来笔者将继续挖掘.
NSObject有两个方法 分别是 - (void)willChangeValueForKey:(NSString )key; 和- (void)didChangeValueForKey:(NSString )key ;
当有值发生变化的时候系统会调用这个两个方法
接下来我们重写Person类的这两个方法, 来验证KVO的调用过程, 代码如下
@implementation Person
- (void)setName:(NSString *)name
_name = name;
NSLog(@"%@", name);
- (void)willChangeValueForKey:(NSString *)key
NSLog(@"willChange前");
[super willChangeValueForKey:key];
NSLog(@"willChange后");
- (void)didChangeValueForKey:(NSString *)key
NSLog(@"didChange前");
[super didChangeValueForKey:key];
NSLog(@"didChange后");
@end
运行结果如下
2018-09-05 13:22:24.169036+0800 KVODemo[32769:24109458] lisi
2018-09-05 13:22:24.169173+0800 KVODemo[32769:24109458] 添加KVO之前的类: Person, 父类:NSObject
2018-09-05 13:22:24.169239+0800 KVODemo[32769:24109458] 添加KVO之前的setter地址0x10e704330
2018-09-05 13:22:24.169510+0800 KVODemo[32769:24109458] willChange前
2018-09-05 13:22:24.169599+0800 KVODemo[32769:24109458] willChange后
2018-09-05 13:22:24.169655+0800 KVODemo[32769:24109458] zhangsan
2018-09-05 13:22:24.169726+0800 KVODemo[32769:24109458] didChange前
2018-09-05 13:22:24.169878+0800 KVODemo[32769:24109458]
kind = 1;
new = zhangsan;
old = lisi;
2018-09-05 13:22:24.169989+0800 KVODemo[32769:24109458] didChange后
2018-09-05 13:22:24.170089+0800 KVODemo[32769:24109458] 添加KVO之后的类名: NSKVONotifying_Person, 父类:Person
2018-09-05 13:22:24.170168+0800 KVODemo[32769:24109458] 添加KVO之后的setter地址0x10ea5d9fa
当name的值修改为zhangsan时, 系统生成一个子类, NSKVONotifying_Person, 并且重写了setName:, didChangeValueForKey: 以及class方法, 这样在调用setName的时候, 去调用didChangeValueForKey并添加观察者的通知, 而我们 调用[person class]时返回的是Person, 说明在子类中也重写了class方法并返回Person.
以上是对KVO的简单分析, 如有错误之处请各位读者不吝指教!
以上是关于细说OC中的KVO的主要内容,如果未能解决你的问题,请参考以下文章