KVO索引值觀察的具體實現,kvo索引值觀察
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 的屬性設定方式來變更屬性值。