小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

(譯)KVO的內(nèi)部實(shí)現(xiàn)

 Rdxer館 2015-09-01
09年的一篇文章,比較深入地闡述了KVO的內(nèi)部實(shí)現(xiàn)。
KVO是實(shí)現(xiàn)Cocoa Bindings的基礎(chǔ),它提供了一種方法,當(dāng)某個(gè)屬性改變時(shí),相應(yīng)的objects會(huì)被通知到。在其他語(yǔ)言中,這種觀察者模式通常需要單獨(dú)實(shí)現(xiàn),而在Objective-C中,通常無(wú)須增加額外代碼即可使用。
概覽
這是怎么實(shí)現(xiàn)的呢?其實(shí)這都是通過(guò)Objective-C強(qiáng)大的運(yùn)行時(shí)(runtime)實(shí)現(xiàn)的。當(dāng)你第一次觀察某個(gè)object時(shí),runtime會(huì)創(chuàng)建一個(gè)新的繼承原先class的subclass。在這個(gè)新的class中,它重寫(xiě)了所有被觀察的key,然后將object的isa指針指向新創(chuàng)建的class(這個(gè)指針告訴Objective-C運(yùn)行時(shí)某個(gè)object到底是哪種類型的object)。所以object神奇地變成了新的子類的實(shí)例。
這些被重寫(xiě)的方法實(shí)現(xiàn)了如何通知觀察者們。當(dāng)改變一個(gè)key時(shí),會(huì)觸發(fā)setKey方法,但這個(gè)方法被重寫(xiě)了,并且在內(nèi)部添加了發(fā)送通知機(jī)制。(當(dāng)然也可以不走setXXX方法,比如直接修改iVar,但不推薦這么做)。
有意思的是:蘋(píng)果不希望這個(gè)機(jī)制暴露在外部。除了setters,這個(gè)動(dòng)態(tài)生成的子類同時(shí)也重寫(xiě)了-class方法,依舊返回原先的class!如果不仔細(xì)看的話,被KVO過(guò)的object看起來(lái)和原先的object沒(méi)什么兩樣。
深入探究
下面來(lái)看看這些是如何實(shí)現(xiàn)的。我寫(xiě)了個(gè)程序來(lái)演示隱藏在KVO背后的機(jī)制。
  1. // gcc -o kvoexplorer -framework Foundation kvoexplorer.m 
  2.      
  3. #import <Foundation/Foundation.h> 
  4. #import <objc/runtime.h> 
  5.  
  6.  
  7. @interface TestClass : NSObject 
  8.     int x; 
  9.     int y; 
  10.     int z; 
  11. @property int x; 
  12. @property int y; 
  13. @property int z; 
  14. @end 
  15.  
  16. @implementation TestClass 
  17. @synthesize x, y, z; 
  18. @end 
  19.  
  20. static NSArray *ClassMethodNames(Class c) 
  21.     NSMutableArray *array = [NSMutableArray array]; 
  22.      
  23.     unsigned int methodCount = 0; 
  24.     Method *methodList = class_copyMethodList(c, &methodCount); 
  25.     unsigned int i; 
  26.     for(i = 0; i < methodCount; i++) 
  27.         [array addObject: NSStringFromSelector(method_getName(methodList[i]))]; 
  28.     free(methodList); 
  29.      
  30.     return array; 
  31.  
  32. static void PrintDescription(NSString *name, id obj) 
  33.     NSString *str = [NSString stringWithFormat: 
  34.         @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>", 
  35.         name, 
  36.         obj, 
  37.         class_getName([obj class]), 
  38.         class_getName(obj->isa), 
  39.         [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]]; 
  40.     printf("%s\n", [str UTF8String]); 
  41.  
  42. int main(int argc, char **argv) 
  43.     [NSAutoreleasePool new]; 
  44.      
  45.     TestClass *x = [[TestClass alloc] init]; 
  46.     TestClass *y = [[TestClass alloc] init]; 
  47.     TestClass *xy = [[TestClass alloc] init]; 
  48.     TestClass *control = [[TestClass alloc] init]; 
  49.      
  50.     [x addObserver:x forKeyPath:@"x" options:0 context:NULL]; 
  51.     [xy addObserver:xy forKeyPath:@"x" options:0 context:NULL]; 
  52.     [y addObserver:y forKeyPath:@"y" options:0 context:NULL]; 
  53.     [xy addObserver:xy forKeyPath:@"y" options:0 context:NULL]; 
  54.      
  55.     PrintDescription(@"control", control); 
  56.     PrintDescription(@"x", x); 
  57.     PrintDescription(@"y", y); 
  58.     PrintDescription(@"xy", xy); 
  59.      
  60.     printf("Using NSObject methods, normal setX: is %p, overridden setX: is %p\n", 
  61.           [control methodForSelector:@selector(setX:)], 
  62.           [x methodForSelector:@selector(setX:)]); 
  63.     printf("Using libobjc functions, normal setX: is %p, overridden setX: is %p\n", 
  64.           method_getImplementation(class_getInstanceMethod(object_getClass(control), 
  65.                                    @selector(setX:))), 
  66.           method_getImplementation(class_getInstanceMethod(object_getClass(x), 
  67.                                    @selector(setX:)))); 
  68.      
  69.     return 0; 
我們從頭到尾細(xì)細(xì)看來(lái)。
首先定義了一個(gè)TestClass的類,它有3個(gè)屬性。
然后定義了一些方便調(diào)試的方法。ClassMethodNames使用Objective-C運(yùn)行時(shí)方法來(lái)遍歷一個(gè)class,得到方法列表。注意,這些方法不包括父類的方法。PrintDescription打印object的所有信息,包括class信息(包括-class和通過(guò)運(yùn)行時(shí)得到的class),以及這個(gè)class實(shí)現(xiàn)的方法。
然后創(chuàng)建了4個(gè)TestClass實(shí)例,每一個(gè)都使用了不同的觀察方式。x實(shí)例有一個(gè)觀察者觀察xkey,y, xy也類似。為了做比較,zkey沒(méi)有觀察者。最后control實(shí)例沒(méi)有任何觀察者。
然后打印出4個(gè)objects的description。
之后繼續(xù)打印被重寫(xiě)的setter內(nèi)存地址,以及未被重寫(xiě)的setter的內(nèi)存地址做比較。這里做了兩次,是因?yàn)?methodForSelector:沒(méi)能得到重寫(xiě)的方法。KVO試圖掩蓋它實(shí)際上創(chuàng)建了一個(gè)新的subclass這個(gè)事實(shí)!但是使用運(yùn)行時(shí)的方法就原形畢露了。
運(yùn)行代碼
看看這段代碼的輸出
  1. control: <TestClass: 0x104b20> 
  2.     NSObject class TestClass 
  3.     libobjc class TestClass 
  4.     implements methods <setX:, x, setY:, y, setZ:, z> 
  5. x: <TestClass: 0x103280> 
  6.     NSObject class TestClass 
  7.     libobjc class NSKVONotifying_TestClass 
  8.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  9. y: <TestClass: 0x104b00> 
  10.     NSObject class TestClass 
  11.     libobjc class NSKVONotifying_TestClass 
  12.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  13. xy: <TestClass: 0x104b10> 
  14.     NSObject class TestClass 
  15.     libobjc class NSKVONotifying_TestClass 
  16.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  17. Using NSObject methods, normal setX: is 0x195e, overridden setX: is 0x195e 
  18. Using libobjc functions, normal setX: is 0x195e, overridden setX: is 0x96a1a550 
首先,它輸出了controlobject,沒(méi)有任何問(wèn)題,它的class是TestClass,并且實(shí)現(xiàn)了6個(gè)set/get方法。
然后是3個(gè)被觀察的objects。注意-class仍然顯示的是TestClass,使用object_getClass顯示了這個(gè)object的真面目:它是NSKVONotifying_TestClass的一個(gè)實(shí)例。這個(gè)NSKVONotifying_TestClass就是動(dòng)態(tài)生成的subclass!
注意,它是如何實(shí)現(xiàn)這兩個(gè)被觀察的setters的。你會(huì)發(fā)現(xiàn),它很聰明,沒(méi)有重寫(xiě)-setZ:,雖然它也是個(gè)setter,因?yàn)樗鼪](méi)有被觀察。同時(shí)注意到,3個(gè)實(shí)例對(duì)應(yīng)的是同一個(gè)class,也就是說(shuō)兩個(gè)setters都被重寫(xiě)了,盡管其中的兩個(gè)實(shí)例只觀察了一個(gè)屬性。這會(huì)帶來(lái)一點(diǎn)效率上的問(wèn)題,因?yàn)榧词箾](méi)有被觀察的property也會(huì)走被重寫(xiě)的setter,但蘋(píng)果顯然覺(jué)得這比分開(kāi)生成動(dòng)態(tài)的subclass更好,我也覺(jué)得這是個(gè)正確的選擇。
你會(huì)看到3個(gè)其他的方法。有之前提到過(guò)的被重寫(xiě)的-class方法,假裝自己還是原來(lái)的class。還有-dealloc方法處理一些收尾工作。還有一個(gè)_isKVOA方法,看起來(lái)像是一個(gè)私有方法。
接下來(lái),我們輸出-setX:的實(shí)現(xiàn)。使用-methodForSelector:返回的是相同的值。因?yàn)?setX:已經(jīng)在子類被重寫(xiě)了,這也就意味著methodForSelector:在內(nèi)部實(shí)現(xiàn)中使用了-class,于是得到了錯(cuò)誤的結(jié)果。
最后我們通過(guò)運(yùn)行時(shí)得到了不同的輸出結(jié)果。
作為一個(gè)優(yōu)秀的探索者,我們進(jìn)入debugger來(lái)看看這第二個(gè)方法的實(shí)現(xiàn)到底是怎樣的:
  1. (gdb) print (IMP)0x96a1a550 
  2. $1 = (IMP) 0x96a1a550 <_NSSetIntValueAndNotify> 
看起來(lái)是一個(gè)內(nèi)部方法,對(duì)Foundation使用nm -a得到一個(gè)完整的私有方法列表:
  1. 0013df80 t __NSSetBoolValueAndNotify 
  2. 000a0480 t __NSSetCharValueAndNotify 
  3. 0013e120 t __NSSetDoubleValueAndNotify 
  4. 0013e1f0 t __NSSetFloatValueAndNotify 
  5. 000e3550 t __NSSetIntValueAndNotify 
  6. 0013e390 t __NSSetLongLongValueAndNotify 
  7. 0013e2c0 t __NSSetLongValueAndNotify 
  8. 00089df0 t __NSSetObjectValueAndNotify 
  9. 0013e6f0 t __NSSetPointValueAndNotify 
  10. 0013e7d0 t __NSSetRangeValueAndNotify 
  11. 0013e8b0 t __NSSetRectValueAndNotify 
  12. 0013e550 t __NSSetShortValueAndNotify 
  13. 0008ab20 t __NSSetSizeValueAndNotify 
  14. 0013e050 t __NSSetUnsignedCharValueAndNotify 
  15. 0009fcd0 t __NSSetUnsignedIntValueAndNotify 
  16. 0013e470 t __NSSetUnsignedLongLongValueAndNotify 
  17. 0009fc00 t __NSSetUnsignedLongValueAndNotify 
  18. 0013e620 t __NSSetUnsignedShortValueAndNotify 
這個(gè)列表也能發(fā)現(xiàn)一些有趣的東西。比如蘋(píng)果為每一種primitive type都寫(xiě)了對(duì)應(yīng)的實(shí)現(xiàn)。Objective-C的object會(huì)用到的其實(shí)只有__NSSetObjectValueAndNotify,但需要一整套來(lái)對(duì)應(yīng)剩下的,而且看起來(lái)也沒(méi)有實(shí)現(xiàn)完全,比如long dobule或_Bool都沒(méi)有。甚至沒(méi)有為通用指針類型(generic pointer type)提供方法。所以,不在這個(gè)方法列表里的屬性其實(shí)是不支持KVO的。
KVO是一個(gè)很強(qiáng)大的工具,有時(shí)候過(guò)于強(qiáng)大了,尤其是有了自動(dòng)觸發(fā)通知機(jī)制?,F(xiàn)在你知道它內(nèi)部是怎么實(shí)現(xiàn)的了,這些知識(shí)或許能幫助你更好地使用它,或在它出錯(cuò)時(shí)更方便調(diào)試。
如果你打算使用KVO,或許可以看一下我的另一篇文章Key-Value Observing Done Right

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多