iOS-详解KVO底层实现
前言
KVO: Key-Value-Observer,它来源于观察者模式, 其基本思想(copy于某度
)是一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。
本质
当某个类的实例对象的key第一次被观察时,系统就会在运行期动态地创建该类的一个派生类NSKVONotifying_类名
,在这个派生类中重写该类中被观察的属性的 setter 方法。
一步一步验证
- ①. KVO的本质就是监听对象的属性进行赋值的时候有没有调用
setter
方法. 如果有调用setter
方法, 就会接收到属性变更的通知, 反之则没有.
我们新建一个类Person
, 定义一个属性name
, 并自己实现它的setter方法
1 @property(nonatomic, copy) NSString *name; 2 3 - (void)setName:(NSString *)name 4 { 5 _name = [name copy]; 6 7 NSLog(@"%s", __FUNCTION__); 8 }
随后, 我们在ViewController
中添加Person
的实例对象的属性name
的属性监听, 暨添加KVO
, 并实现处理变更通知方法 observeValueForKeyPath
1 self.p = [[Person alloc] init]; 2 [self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
1 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 2 { 3 NSLog(@"%@, %@", keyPath, self.p.name); 4 }
此时, 我们在点击屏幕的时候, 给实例对象p的name进行赋值
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 2 static int i = 0; 3 self.p.name = [NSString stringWithFormat:@"name->%d", i++]; 4 }
运行程序, 点击两次屏幕, 我们可以可以观察到打印如下
1 -[Person setName:] 2 name, name->0 3 4 -[Person setName:] 5 name, name->1
我们再进行一次反证, 我们不使用setter
方法对name
进行赋值, 看是否属性的值改变会被监听
我们将Person.h
修改如下
1 @interface Person : NSObject 2 { 3 @public NSString *_name; 4 } 5 @property(nonatomic, copy) NSString *name; 6 @end
此时, @property
只会帮我们生成getter方法(setter
已经被我们自己实现了的), 不会生产_name
成员变量
我们修改点击屏幕的代码如下
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 2 static int i = 0; 3 self.p->_name = [NSString stringWithFormat:@"name->%d", i++]; 4 }
此时我们运行程序会发现, 不管怎么点击屏幕, 控制台都不会有任何打印.
结论:
KVO的本质就是监听对象的属性进行赋值的时候有没有调用`setter`方法
② 当某个类的实例对象的key第一次被观察时,系统就会在运行期动态地创建该类的一个派生类NSKVONotifying_类名
,在这个派生类中重写该类中被观察的属性的 setter
方法。
如果我们要重写方法, 一般都会调用[super 方法名]
, 那么系统是怎么做到重写属性的setter方法, 且通知观察者Observer
的呢? 下面我们一步一步来进行分析:
如图所示, 我们有断点A和B, 此时我们运行程序, 程序在停留在断点A处, 我们观察isa
指针
此时,isa
指针指向Person类
我们跳过这个断点, continue
, 并点击屏幕, 此时程序停留到了断点B, 此时我们再次观察isa
此时isa指针被系统动态的指向了派生类NSKVONotifying_Person
. 由此, 结论被证.
自己实现KVO
由于系统是自动实现的派生类NSKVONotifying_Person
, 这儿我们自己手动创建一个派生类ALINKVONotifying_Person
, 集成自Person
. 同时给NSObject
创建一个分类, 让每一个对象都拥有我们自定义的KVO特性.
NSObject+KVO.h
1
2
3
4
5
|
#import <Foundation/Foundation.h> @ interface NSObject (KVO)
- ( void )alin_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end |
NSObject+KVO.m
1 #import "NSObject+KVO.h" 2 #import "ALINKVONotifying_Person.h" 3 #import <objc/message.h> 4 5 NSString *const ObserverKey = @"ObserverKey"; 6 7 @implementation NSObject (KVO) 8 9 // 仿系统的, 前缀是为了区别系统的 10 - (void)alin_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context 11 { 12 // 把观察者保存到当前对象 13 objc_setAssociatedObject(self, (__bridge const void *)(ObserverKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 14 15 // 修改对象isa指针 16 object_setClass(self, [ALINKVONotifying_Person class]); 17 } 18 @end
ALINKVONotifying_Person.m
1 #import "ALINKVONotifying_Person.h" 2 #import <objc/runtime.h> 3 4 extern NSString *const ObserverKey; 5 6 @implementation ALINKVONotifying_Person 7 - (void)setName:(NSString *)name 8 { 9 [super setName:name]; 10 11 // 获取观察者 12 id obsetver = objc_getAssociatedObject(self, ObserverKey); 13 14 [obsetver observeValueForKeyPath:@"name" ofObject:self change:nil context:nil]; 15 } 16 @end
此时我们调用自己定义的监听方法, 效果和系统的也是一样的
1 [self.p alin_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
文/Monkey_ALin(简书作者)
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
三、实现原理
1、KVC如何访问属性值
2、KVC/KVO实现原理
派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而**键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
- NSLog(@"self->isa:%@",self->isa);
- NSLog(@"self class:%@",[self class]);
- self->isa:Person
- self class:Person
- self->isa:NSKVONotifying_Person
- self class:Person
- - (void)willChangeValueForKey:(NSString *)key
- - (void)didChangeValueForKey:(NSString *)key
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context
- - (void)willChangeValueForKey:(NSString *)key
- - (void)didChangeValueForKey:(NSString *)key
上一篇: iOS-解析KVO的底层实现