首先咱們來(lái)看下蘋果的開(kāi)發(fā)者文檔。
NSKeyValueObserving
An informal protocol that objects adopt to be notified of changes to the specified properties of other objects.
大致翻譯:一種非正式協(xié)議,通知其他對(duì)象的指定屬性發(fā)生了改變。
可以理解為,用來(lái)監(jiān)聽(tīng)某個(gè)對(duì)象的屬性發(fā)生改變時(shí),做出的一種通知機(jī)制。
那么問(wèn)題來(lái)了,什么是非正式協(xié)議了,KVC也說(shuō)是非正式協(xié)議,但還是不理解是什么意思。隨機(jī)百度了下,非正式協(xié)議
非正式協(xié)議(informal protocol):所謂的非正式協(xié)就是類別,即凡是NSObject或子類的類別,都是非正式協(xié)議。
正式協(xié)議(protocal):指的是一個(gè)以@protocol方式命名的方法列表,與非正式協(xié)議不同的是,它要求顯示的采用協(xié)議。你可以使用@required或者optional關(guān)鍵字指定方法是否必須實(shí)現(xiàn)。子類繼承父類采用的協(xié)議。正式協(xié)議也可以遵守其他協(xié)議。
概覽
KVO即NSKeyValueObserving也叫鍵值觀察或鍵值監(jiān)聽(tīng),是蘋果提供的一套事件監(jiān)聽(tīng)機(jī)制。它其實(shí)就是非正式協(xié)議NSKeyValueObserving.h中定義的一種通知機(jī)制。
允許對(duì)象監(jiān)聽(tīng)另一個(gè)對(duì)象特定屬性的改變,并在改變時(shí)接收到事件。由于KVO的實(shí)現(xiàn)機(jī)制,所以對(duì)屬性才會(huì)發(fā)生作用,一般繼承自NSObject的對(duì)象都默認(rèn)支持KVO。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系,KVO是一對(duì)一的,而不一對(duì)多的。KVO對(duì)被監(jiān)聽(tīng)對(duì)象無(wú)侵入性,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽(tīng)。
KVO可以監(jiān)聽(tīng)單個(gè)屬性的變化,也可以監(jiān)聽(tīng)集合對(duì)象的變化。通過(guò)KVC的mutableArrayValueForKey:等方法獲得代理對(duì)象,當(dāng)代理對(duì)象的內(nèi)部對(duì)象發(fā)生改變時(shí),會(huì)回調(diào)KVO監(jiān)聽(tīng)的方法。集合對(duì)象包含NSArray和NSSet。
簡(jiǎn)單使用
添加KVO觀察和移除觀察
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//移除某個(gè)對(duì)象的屬性值,當(dāng)對(duì)某個(gè)對(duì)象的屬性有多個(gè)監(jiān)聽(tīng)時(shí),需要添加context,以區(qū)分具體移除某個(gè)上下文環(huán)境中的KVO。
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
注意KVO的添加和移除一定是成對(duì)出現(xiàn),添加了沒(méi)移除,或者移除了沒(méi)添加過(guò)的監(jiān)聽(tīng)都會(huì)crash。
蘋果推薦的方式是init的時(shí)候addObserver,在dealloc的時(shí)候removeObserver,可以保證添加和移除是成對(duì)出現(xiàn)的。
同時(shí)了,添加了監(jiān)聽(tīng),一定要實(shí)現(xiàn)監(jiān)聽(tīng)方法,不然也會(huì)crash。
/// KVO監(jiān)聽(tīng)回調(diào)方法
/// @param keyPath 監(jiān)聽(tīng)對(duì)象的屬性
/// @param object 被監(jiān)聽(tīng)的對(duì)象
/// @param change 所監(jiān)聽(tīng)屬性值的變化
/// @param context 對(duì)應(yīng)監(jiān)聽(tīng)對(duì)象的上下文環(huán)境,(某個(gè)對(duì)象的屬性監(jiān)聽(tīng)可以添加多次,如果想對(duì)某一次進(jìn)行單獨(dú)處理,則需要根據(jù)此字段來(lái)區(qū)分
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"觀察監(jiān)聽(tīng)返回\n keyPath:%@ --- %@",keyPath, change);
}
簡(jiǎn)單代碼演示:
KVOObject *kvoObj = [[KVOObject alloc]init];
kvoObj.name = @"intialName"; //NSKeyValueObservingOptionInitial
/**
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01, 添加監(jiān)聽(tīng)后,賦值的最新值
NSKeyValueObservingOptionOld = 0x02, 監(jiān)聽(tīng)變化之前的值
NSKeyValueObservingOptionInitial = 0x04 監(jiān)聽(tīng)對(duì)象的初始值,一般是添加監(jiān)聽(tīng)前的值
NSKeyValueObservingOptionPrior = 0x08 此類型有值改變時(shí),會(huì)觸發(fā)兩次監(jiān)聽(tīng)方法。
};
*/
[kvoObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"vc"];
kvoObj.name = @"hello";
/// KVO監(jiān)聽(tīng)回調(diào)方法
/// @param keyPath 監(jiān)聽(tīng)對(duì)象的屬性
/// @param object 被監(jiān)聽(tīng)的對(duì)象
/// @param change 所監(jiān)聽(tīng)屬性值的變化
/// @param context 對(duì)應(yīng)監(jiān)聽(tīng)對(duì)象的上下文環(huán)境,(某個(gè)對(duì)象的屬性監(jiān)聽(tīng)可以添加多次,如果想對(duì)某一次進(jìn)行單獨(dú)處理,則需要根據(jù)此字段來(lái)區(qū)分
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"觀察監(jiān)聽(tīng)返回\n keyPath:%@ \n change: %@",keyPath, change);
if ([object isKindOfClass:[KVOObject class]] && [keyPath isEqualToString:@"name"]) {
//此處進(jìn)行邏輯處理
return;
}
}
其打印結(jié)果:
2020-11-13 14:41:53.091733+0800 KVO原理剖析[13183:169798] 觀察監(jiān)聽(tīng)返回
keyPath:name
change: {
kind = 1;
new = hello;
old = intialName;
}
2020-11-13 14:49:41.791428+0800 KVO原理剖析[13345:173436] 監(jiān)聽(tīng)前: KVOObject
2020-11-13 14:49:41.791812+0800 KVO原理剖析[13345:173436] 監(jiān)聽(tīng)后: NSKVONotifying_KVOObject
2020-11-13 15:37:53.522677+0800 KVO原理剖析[14775:200295] setName:
2020-11-13 15:37:53.522908+0800 KVO原理剖析[14775:200295] class
2020-11-13 15:37:53.523024+0800 KVO原理剖析[14775:200295] dealloc
2020-11-13 15:37:53.523160+0800 KVO原理剖析[14775:200295] _isKVOA