KVO底層實現原理

2021-07-03 23:23:10 字數 4766 閱讀 6528

kvo是實現cocoa bindings的基礎,它提供了一種方法,當某個屬性改變時,相對的objects會被通知到。在其他語言中,這種觀察者模式通常需要單獨實現,而在objective-c中,通常無須增加額外**即可使用,

這是怎麼實現的呢?其實就是通過oc強大的執行時(runtime)實現的。當你第一次觀察某個物件時,runtime會建立乙個新的繼承原先class的subclass。在這個新的class中,它重寫了所有被觀察的key,然後將oc的isa指標指向新建立的class(這個指標告訴oc執行時某個object到底是哪種型別的object)。所以object神奇的變成了新的子類的例項;

這些被重寫的方法實現了如何通知觀察者們。當改變乙個key時,會觸發setkey方法,但這個方法被重寫了,並且在內部新增了傳送通知機制。(當然也可以i不走set方法,比如直接修改ivar,但不推薦這麼做)。

有意思的是:蘋果不希望這個機制暴露在外部。除了setters,這個動態生成的子類同時也重寫了-class方法,依舊返回原先的class!如果不仔細看的話,被kvo過的object看起來和原先的object沒什麼兩樣。

下面是筆者寫了個程式來演示隱藏在kvo背後的機制。

// gcc -o kvoexplorer -framework foundation kvoexplorer.m

#import 

#import 

@inte***cetestclass : nsobject 

@propertyintx; 

@propertyinty; 

@propertyintz; 

@end 

@implementation testclass 

@synthesize x, y, z; 

@end 

staticnsarray *classmethodnames(class c) 

static

voidprintdescription(nsstring *name, id obj) 

intmain(

intargc, 

char**argv)  

我們從頭到尾細細看來。

首先定義了乙個testclass的類,它有3個屬性。

然後定義了一些方便除錯的方法。classmethodnames使用objective-c執行時方法來遍歷乙個class,得到方法列表。注意,這些方法不包括父類的方法。printdescription列印object的所有資訊,包括class資訊(包括-class和通過執行時得到的class),以及這個class實現的方法。

然後建立了4個testclass例項,每乙個都使用了不同的觀察方式。x例項有乙個觀察者觀察xkey,y, xy也類似。為了做比較,zkey沒有觀察者。最後control例項沒有任何觀察者。

然後列印出4個objects的description。

之後繼續列印被重寫的setter記憶體位址,以及未被重寫的setter的記憶體位址做比較。這裡做了兩次,是因為-methodforselector:沒能得到重寫的方法。kvo試圖掩蓋它實際上建立了乙個新的subclass這個事實!但是使用執行時的方法就原形畢露了。

執行**

看看這段**的輸出

control: 

nsobjectclasstestclass 

libobjcclasstestclass 

implementsmethods 

x: nsobjectclasstestclass 

libobjcclassnskvonotifying_testclass 

implementsmethodsclass, dealloc, _isk***> 

y: nsobjectclasstestclass 

libobjcclassnskvonotifying_testclass 

implementsmethodsclass, dealloc, _isk***> 

xy: 

nsobjectclasstestclass 

libobjcclassnskvonotifying_testclass 

implementsmethodsclass, dealloc, _isk***> 

using nsobject methods, normal setx: is 0x195e, overridden setx: is 0x195e 

using libobjc functions, normal setx: is 0x195e, overridden setx: is 0x96a1a550 

首先,它輸出了controlobject,沒有任何問題,它的class是testclass,並且實現了6個set/get方法。

然後是3個被觀察的objects。注意-class仍然顯示的是testclass,使用object_getclass顯示了這個object的真面目:它是nskvonotifying_testclass的乙個例項。這個nskvonotifying_testclass就是動態生成的subclass!

注意,它是如何實現這兩個被觀察的setters的。你會發現,它很聰明,沒有重寫-setz:,雖然它也是個setter,因為它沒有被觀察。同時注意到,3個例項對應的是同乙個class,也就是說兩個setters都被重寫了,儘管其中的兩個例項只觀察了乙個屬性。這會帶來一點效率上的問題,因為即使沒有被觀察的property也會走被重寫的setter,但蘋果顯然覺得這比分開生成動態的subclass更好,我也覺得這是個正確的選擇。

你會看到3個其他的方法。有之前提到過的被重寫的-class方法,假裝自己還是原來的class。還有-dealloc方法處理一些收尾工作。還有乙個_isk***方法,看起來像是乙個私有方法。

接下來,我們輸出-setx:的實現。使用-methodforselector:返回的是相同的值。因為-setx:已經在子類被重寫了,這也就意味著methodforselector:在內部實現中使用了-class,於是得到了錯誤的結果。

最後我們通過執行時得到了不同的輸出結果。

作為乙個優秀的探索者,我們進入debugger來看看這第二個方法的實現到底是怎樣的:

(gdb) print (imp)0x96a1a550 

$1 = (imp) 0x96a1a550 <_nssetintvalueandnotify> 

看起來是乙個內部方法,對foundation使用nm -a得到乙個完整的私有方法列表:

0013df80 t __nssetboolvalueandnotify 

000a0480 t __nssetcharvalueandnotify 

0013e120 t __nssetdoublevalueandnotify 

0013e1f0 t __nssetfloatvalueandnotify 

000e3550 t __nssetintvalueandnotify 

0013e390 t __nssetlonglongvalueandnotify 

0013e2c0 t __nssetlongvalueandnotify 

00089df0 t __nssetobjectvalueandnotify 

0013e6f0 t __nssetpointvalueandnotify 

0013e7d0 t __nssetrangevalueandnotify 

0013e8b0 t __nssetrectvalueandnotify 

0013e550 t __nssetshortvalueandnotify 

0008ab20 t __nssetsizevalueandnotify 

0013e050 t __nssetunsignedcharvalueandnotify 

0009fcd0 t __nssetunsignedintvalueandnotify 

0013e470 t __nssetunsignedlonglongvalueandnotify 

0009fc00 t __nssetunsignedlongvalueandnotify 

0013e620 t __nssetunsignedshortvalueandnotify 

這個列表也能發現一些有趣的東西。比如蘋果為每一種primitive type都寫了對應的實現。objective-c的object會用到的其實只有__nssetobjectvalueandnotify,但需要一整套來對應剩下的,而且看起來也沒有實現完全,比如long dobule或_bool都沒有。甚至沒有為通用指標型別(generic pointer type)提供方法。所以,不在這個方法列表裡的屬性其實是不支援kvo的。

kvo是乙個很強大的工具,有時候過於強大了,尤其是有了自動觸發通知機制。現在你知道它內部是怎麼實現的了,這些知識或許能幫助你更好地使用它,或在它出錯時更方便除錯。

iOS 底層實現 KVO

kvo是基於觀察者設計模式來實現的。觀察者模式 乙個目標物件管理所有依賴於它的觀察者物件,並在它自身的狀態改變時主動通知觀察者物件。這個主動通知通常是通過呼叫各觀察者物件所提供的介面方法來實現的。觀察者模式較完美地將目標物件與觀察者物件解耦。手動實現鍵值觀察 示例 被觀察的物件target 重寫se...

KVC和KVO底層原理

kvc和kvo想必都熟知的乙個名詞,觀察者模式,而kvo是基於kvc的,那麼kvc到底是個什麼,kvo又是什麼,那麼它們之間是怎麼關聯的。當你了解kvc機制,會恍然大悟,只要知道乙個ui的結構,就能對他做任意的修改。而kvo是觀察者模式的一種實現,對物件屬性監聽能達到乙個高效能處理,所以了解kvc和...

iOS 模擬KVO的底層實現 手動實現KVO

一 回顧系統的kvo是怎麼實現監聽的 1.例項化乙個類 person person person alloc init person.age 11 2.開始監聽 person addobserver self forkeypath age options nskeyvalueobservingopt...