整理一下自己學(xué)到的知識(shí),方便以后查看
1.向nil對象發(fā)送消息易迹,為什么不會(huì)崩潰
每個(gè)想法在運(yùn)行時(shí)夭委,都會(huì)被動(dòng)態(tài)轉(zhuǎn)為消息發(fā)送,即ojbc_msgSend(receiver,selector)能岩。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class寞宫,因?yàn)镺bjc的類的本身也是一個(gè)Object,為了處理這個(gè)關(guān)系拉鹃,runtime就創(chuàng)造了Meta Class辈赋,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時(shí),實(shí)際上是把這個(gè)消息發(fā)給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息膏燕,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息钥屈,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對象坝辫,這時(shí)會(huì)在method Lists中遍歷篷就,如果cache了,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率近忙。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
向?qū)ο蟀l(fā)送消息時(shí)竭业,runtime會(huì)根據(jù)對象的isa指針找到它的類對象,從類對象中方法列表以及其父類方法列表中尋找方法運(yùn)行银锻,發(fā)送信息是objc_msgSend方法不會(huì)返回值永品,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的。 如果對一個(gè)nil對象發(fā)送信息击纬,對象的isa指針就是返回0地址了鼎姐,所以不會(huì)出現(xiàn)崩潰。
轉(zhuǎn)載一張非常有用且非常利于理解的圖:
KVO實(shí)現(xiàn)原理(isa混寫)
當(dāng)你觀察一個(gè)對象時(shí)更振,一個(gè)新的類會(huì)被動(dòng)態(tài)創(chuàng)建炕桨。這個(gè)類繼承自該對象的原本的類,并重寫了被觀察屬性的 setter 方法肯腕。重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后献宫,通知所有觀察對象:值的更改。最后通過 isa 混寫(isa-swizzling) 把這個(gè)對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類实撒,對象就神奇的變成了新創(chuàng)建的子類的實(shí)例姊途。我畫了一張示意圖,如下所示:
KVO 確實(shí)有點(diǎn)黑魔法:
Apple 使用了 isa 混寫(isa-swizzling)來實(shí)現(xiàn) KVO 知态。
下面做下詳細(xì)解釋:
鍵值觀察通知依賴于 NSObject 的兩個(gè)方法: willChangeValueForKey: 和 didChangevlueForKey: 捷兰。在一個(gè)被觀察屬性發(fā)生改變之前, willChangeValueForKey: 一定會(huì)被調(diào)用负敏,這就 會(huì)記錄舊的值贡茅。而當(dāng)改變發(fā)生后, didChangeValueForKey: 會(huì)被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用顶考×藁梗可以手動(dòng)實(shí)現(xiàn)這些調(diào)用,但很少有人這么做驹沿。一般我們只在希望能控制回調(diào)的調(diào)用時(shí)機(jī)時(shí)才會(huì)這么做艘策。大部分情況下,改變通知會(huì)自動(dòng)調(diào)用甚负。
比如調(diào)用 setNow: 時(shí)柬焕,系統(tǒng)還會(huì)以某種方式在中間插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的調(diào)用梭域。大家可能以為這是因?yàn)?setNow: 是合成方法斑举,有時(shí)候我們也能看到人們這么寫代碼:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"]; // 沒有必要
_now = aDate;
[self didChangeValueForKey:@"now"];// 沒有必要
}
這是完全沒有必要的代碼,不要這么做病涨,這樣的話富玷,KVO代碼會(huì)被調(diào)用兩次。KVO在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey: 既穆,之后總是調(diào)用 didChangeValueForkey: 赎懦。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。第一次對一個(gè)對象調(diào)用 addObserver:forKeyPath:options:context: 時(shí)幻工,框架會(huì)創(chuàng)建這個(gè)類的新的 KVO 子類励两,并將被觀察對象轉(zhuǎn)換為新子類的對象。在這個(gè) KVO 特殊子類中囊颅, Cocoa 創(chuàng)建觀察屬性的 setter 当悔,大致工作原理如下:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
這種繼承和方法注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。這就是正確命名如此重要的原因踢代。只有在使用KVC命名約定時(shí)盲憎,KVO才能做到這一點(diǎn)。
KVO 在實(shí)現(xiàn)中通過 isa 混寫(isa-swizzling) 把這個(gè)對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類胳挎,對象就神奇的變成了新創(chuàng)建的子類的實(shí)例饼疙。這在Apple 的文檔可以得到印證:
Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...
然而 KVO 在實(shí)現(xiàn)中使用了 isa 混寫( isa-swizzling) ,這個(gè)的確不是很容易發(fā)現(xiàn):Apple 還重寫慕爬、覆蓋了 -class 方法并返回原來的類窑眯。 企圖欺騙我們:這個(gè)類沒有變,就是原本那個(gè)類医窿。伸但。。
但是留搔,假設(shè)“被監(jiān)聽的對象”的類對象是 MYClass ,有時(shí)候我們能看到對 NSKVONotifying_MYClass 的引用而不是對 MYClass 的引用铛铁。借此我們得以知道 Apple 使用了 isa 混寫(isa-swizzling)隔显。具體探究過程可參考 這篇博文 却妨。