1、KVO的簡介
KVO 全稱 Key-Value Observing。中文叫鍵值觀察。KVO其實是一種觀察者模式,觀察者在鍵值改變時會得到通知,利用它可以很容易實現視圖組件和數據模型的分離,當數據模型的屬性值改變之后作為監聽器的視圖組件就會被激發,激發時就會回調監聽器自身。相比Notification,KVO更加的簡單直接。
KVO的操作方法由NSKeyValueCoding提供,而他是NSObject的類別,也就是說ObjC中幾乎所有的對象都支持KVO操作。
KVO 需要實現實例變量的 setter/getter方法
2、KVO的實現
0) KVO的使用也很簡單,就是簡單的3步。
(1)注冊需要觀察的對象的屬性addObserver:forKeyPath:options:context:
(2)實現observeValueForKeyPath:ofObject:change:context:方法,這個方法當觀察的屬性變化時會自動調用.在這個方法中還通過 NSKeyValueObservingOptionNew這個參數要求把新值在dictionary中傳遞過來。
(3)取消注冊觀察removeObserver:forKeyPath:context:
1)注冊
實現方法:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
方法參數:
object : 被觀察對象
observer: 觀察對象
forKeyPath里面帶上property的name,如UIView的frame、center等等
options: 有4個值,分別是:
NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法
NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法
NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。
NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后。
注:例子里的0就代表不帶任何參數進去
context: 可以帶入一些參數,其實這個挺好用的,任何類型都可以,自己強轉就好了。
2)監測
實現方法:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
這個方法當觀察的屬性變化時會自動調用.在這個方法中還通過NSKeyValueObservingOptionNew這個參數要求把新值在dictionary中傳遞過來。
方法參數:
keyPath: 對應forKeyPath
object: 被觀察的對象
change: 對應options里的NSKeyValueObservingOptionNew、NSKeyValueObservingOptionOld等
context: 對應context
3)取消注冊觀察
實現方法:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
方法參數:
同上
3、具體實現代碼
main.m

1 // 2 // main.m 3 // KVO具體實現 4 // 5 // Created by ma c on 16/5/18. 6 // Copyright © 2016年 彭盛凇. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 #import "Person.h" 11 #import "Watch.h" 12 13 int main(int argc, const char * argv[]) { 14 @autoreleasepool { 15 16 Person *person = [[Person alloc] init]; 17 18 Watch *watch = [[Watch alloc] init]; 19 20 person.watch = watch; 21 22 [person registerObserver]; 23 24 person.name = @"pss"; 25 26 person.name = @"pbb"; 27 28 } 29 return 0; 30 }
person.h

1 // 2 // Person.h 3 // KVO具體實現 4 // 5 // Created by ma c on 16/5/18. 6 // Copyright © 2016年 彭盛凇. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 #import "Watch.h" 11 12 @interface Person : NSObject 13 14 @property (nonatomic, copy) NSString *name; 15 16 @property (nonatomic, strong) Watch *watch; 17 18 - (void)registerObserver; 19 20 @end
Person.m

1 // 2 // Person.m 3 // KVO具體實現 4 // 5 // Created by ma c on 16/5/18. 6 // Copyright © 2016年 彭盛凇. All rights reserved. 7 // 8 9 #import "Person.h" 10 11 @implementation Person 12 13 - (void)dealloc { 14 15 [self removeObserver:self.watch forKeyPath:@"name"]; 16 } 17 18 - (void)registerObserver { 19 20 [self addObserver:self.watch forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 21 } 22 @end
Watch.m

1 // 2 // Watch.m 3 // KVO具體實現 4 // 5 // Created by ma c on 16/5/18. 6 // Copyright © 2016年 彭盛凇. All rights reserved. 7 // 8 9 #import "Watch.h" 10 11 @implementation Watch 12 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { 13 14 NSLog(@"new = %@",[change valueForKey:@"new"]); 15 16 NSLog(@"old = %@",[change valueForKey:@"old"]); 17 18 NSLog(@"---------------------------"); 19 } 20 @end
Log打印日志
4、KVO常見崩潰錯誤與解決方式
1)沒有在准確位置實現dealloc , 注冊,刪除,監聽 操作方法
2)沒有實現dealloc中的remove操作
5、手動實現KVO
1 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { 2 3 BOOL automatic = YES; 4 if ([key isEqualToString:@"age"]) { 5 automatic = NO; 6 } else { 7 automatic = [super automaticallyNotifiesObserversForKey:key]; 8 } 9 10 return automatic; 11 } 12 13 - (void)setAge:(int)age { 14 15 //手動設置KVO 16 17 if (_age != age) { 18 19 [self willChangeValueForKey:@"age"]; 20 21 _age = age; 22 23 [self didChangeValueForKey:@"age"]; 24 25 } 26 }
6、KVO的實現原理
當某一個類的實例第一次使用KVO的時候,系統就會在運行期間動態的創建該類的一個派生類,該類的命名規則一般是以NSKVONotifying為前綴,以原本的類名為后綴。並且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現真正的通知機制,正如前面我們手動實現KVO一樣。這么做是基於設置屬性會調用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值,如果僅是直接修改屬性對應的成員變量,是無法實現 KVO 的。