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是直接通知到觀察對象,並且邏輯非常清晰,實現步驟簡單。