KVO的用法、底层实现原理
KVO的用法
KVO也就是key-value-observing(即键值观察),利用一个key来找到某个属性并监听其值得改变。用法如下:
- 添加观察者
- 在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:(通过查阅文档可以知道,绝大多数对象都有这个方法,因为这个方法属于NSObject)
- 移除观察者
//让对象b监听对象a的name属性 //options属性可以选择是哪个 /* NSKeyValueObservingOptionNew =0x01, 新值 * NSKeyValueObservingOptionOld =0x02, 旧值 */ [a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil]; a.name = @"zzz"; #pragma mark - 实现KVO回调方法 /* * 当对象的属性发生改变会调用该方法 * @param keyPath 监听的属性 * @param object 监听的对象 * @param change 新值和旧值 * @param context 额外的数据 */ - (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary<NSString *,id>*)change context:(void *)context{ NSLog(@"%@的值改变了,",keyPath); NSLog(@"change:%@", change); } //最后不要忘记了,去移除observer - (void)dealloc{ [a removeObserver:b forKeyPath:@"name"]; }
KVO键值观察者底层解析
涉及到了runtime,关于isa指针
手动实现键值观察(代码示例)
被观察的对象Target(重写setter/getter方法)
Target.h
@interface Target : NSObject { int age; } // for manual KVO - age- (int) age; - (void) setAge:(int)theAge; @end
Target.m
@implementation Target - (id) init{ self = [super init]; if (nil != self) { age = 10; } return self; } // for manual KVO - age - (int) age{ return age; } - (void) setAge:(int)theAge{ [self willChangeValueForKey:@"age"]; age = theAge; [self didChangeValueForKey:@"age"]; } + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key { if ([key isEqualToString:@"age"]) { return NO; } return [super automaticallyNotifiesObserversForKey:key]; } @end
首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;
其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。
实现原理
KVO的实现是基于runtime运行时的,下面就来详细介绍一下原理:还是这张图:
- 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
- 派生类在被重写的 setter 方法中实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
- 同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
KVO与Notification之间的区别:
notification是需要一个发送notification的对象,一般是notificationCenter,来通知观察者。
KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。