1.KVO簡介 KVO是Objective-C對觀察者設計模式的一種實現,它提供一種機制,指定一個被觀察對象(如A類),當對象中的某個屬性發生變化的時候,對象就會接收到通知,並作出相應的處理。在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通訊。例如:代碼中,在模型類A ...
1.KVO簡介
KVO是Objective-C對觀察者設計模式的一種實現,它提供一種機制,指定一個被觀察對象(如A類),當對象中的某個屬性發生變化的時候,對象就會接收到通知,並作出相應的處理。在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通訊。例如:代碼中,在模型類A創建屬性數據,在控制器中創建觀察者,一旦屬性數據發生改變就收到觀察者收到通知,通過KVO再在控制器使用回調方法處理實現視圖B的更新;
2.實現原理
KVO的實現依賴於Objective-C強大的runtime,KVO的底層實現是監聽setter方法。當觀察某對象A時,KVO動態機制會動態創建一個A類的子類,併為這個新的子類重寫父類的的setter方法。setter方法隨後負責通知觀察對象屬性的變化。
3.深入理解
Apple使用了isa混寫(isa-swizzling)來實現KVO,當觀察對象A的時候(也就是調用對象A註冊觀察者的方法的時候),KVO機制會動態創建一個A的新的子類:NSKVONotifying_A的新,該類繼承自對象A的本類。且KVO會為NSKVONotifying_A重寫其父類的setter方法,setter方法會負責在調用原setter方法之前或之後,通知所有觀察對象屬性值的更改狀況。
addobserver方法內部實現:
在這個方法中,被觀察對象的isa指針從指向原來的A類,被KVO機制修改為指向A類的子類-NSKVONotifying_A,來實現當前類屬性值改變的監聽。
isa指針的作用:每個對象都有一個isa指針,指向該對象的類。它告訴Runtime系統這個對象的類是什麼,所以對象註冊為觀察者的是時候,isa指針會指向新的子類,那麼這個對象就會變成新的子類的對象了。因此,該對象調用setter就會調用已經重寫的setter了,從而激活鍵值通知機制。
子類setter方法剖析:
KVO的鍵值觀察通知依賴於NSObject的兩個方法:willChangeValueForKey didChangeValueForKey,在存取值的前後分別調用的方法。被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後, observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的註入是在運行時而不是編譯時實現的。
4.自己實現KVO
首先創建一個NSObject的類擴展,.h文件中自定一個一個方法,由於目標是自定義實現KVO,所以只需要在系統添加觀察者的方法名前面添加一個首碼,參數值不變。方法實現如下
- (void)CC_addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context{ /* 1.自定義一個NSCCKVO_XXX子類 2.重寫父類的setter,在內部恢復子類的做法,通知觀察者 3.修改self的指針,指向新創建的NSCCKVO_XXX子類 */ //動態生成一個類 NSString*oldClassName =NSStringFromClass([selfclass]); NSString*newClassName = [@"NSCCKVO_"stringByAppendingString:oldClassName]; constchar* name= [newClassNameUTF8String]; //定義一個類 //參數1:繼承的那個類參數2:類名稱 Class ccClass=objc_allocateClassPair([selfclass], name,0); //子類添加setter方法,以setName為例 class_addMethod(ccClass,@selector(setName:), (IMP)setName,"v@:@"); //註冊這個類 objc_registerClassPair(ccClass); //修改self的isa指針 object_setClass(self, ccClass); //綁定observer到self對象中,將觀察者綁定當前對象 objc_setAssociatedObject(self, (__bridgeconstvoid*)@"objc", observer,OBJC_ASSOCIATION_RETAIN_NONATOMIC); } voidsetName(idself,SEL_cmd,NSString*newName){ //調用父類的sett方法 Class superClass =class_getSuperclass([selfclass]); //改變isa指針。為父類,調用set方法 object_setClass(self, superClass); objc_msgSend(self,@selector(setName:),newName); //拿出觀察者 idobserver =objc_getAssociatedObject(self, (__bridgeconstvoid*)@"objc"); //通知觀察者 objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new":newName},nil); //改回子類類型 object_setClass(self, [selfclass]); }
然後在ViewController導入NSObject的類擴展的頭文件
Person*person = [[Personalloc]init]; _person= person; //[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; // 這個方法就是剛纔自己去實現的那個方法[personCC_addObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil]; -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{ NSLog(@"%@的%@值變成了%@",object,keyPath,change[@"new"]); }
此處,自己實現KVO就完成了。觀察者觀察的是屬性,只有遵循 KVO 變更屬性值的方式才會執行KVO的回調方法,例如是否執行了setter方法、或者是否使用了KVC賦值。如果賦值沒有通過setter方法或者KVC,而是直接修改屬性對應的成員變數,例如:僅調用_name = @"newName",這時是不會觸發kvo機制,更加不會調用回調方法的。所以使用KVO機制的前提是遵循 KVO 的屬性設置方式來變更屬性值。