接上篇荠列,我們已經(jīng)大概的聊完了c++的虛函數(shù)實現(xiàn)機制类浪。間接尋址體現(xiàn)在虛函數(shù)表的實現(xiàn)上。虛函數(shù)表由編譯負(fù)責(zé)幫我們維護(hù)肌似。我們來回頭捋一捋函數(shù)調(diào)用過程的變化费就。在c中 函數(shù)名直接被譯為函數(shù)指針(地址),調(diào)用的過程就是直接跳轉(zhuǎn)到目的地址執(zhí)行(當(dāng)然,這個跳轉(zhuǎn)不是普通的命令跳轉(zhuǎn)川队,還包含著cpu寄存器狀態(tài)的壓棧 等等力细,不做細(xì)談。)固额。到了c++中眠蚂,對虛函數(shù)的調(diào)用就有了間接尋址了,這個函數(shù)的調(diào)用過程包括了:
. 1 根據(jù)對象地址找到對象內(nèi)存區(qū)域
. 2 取出位于對象內(nèi)存頭部空間的虛函數(shù)表基址
. 3 查表 找到虛函數(shù)的具體地址
. 4 有了函數(shù)地址 然后就可以調(diào)用了斗躏。
總之 逝慧,間接無非就是建立尋址表,將直接的地址翻譯變?yōu)殚g接尋址啄糙。有了c++的虛函數(shù)其實已經(jīng)可以實現(xiàn)面向?qū)ο笾幸粋€重要的概念:多態(tài)笛臣。但是 程序員是能夠輕易滿足的么?顯然不是啊隧饼。這哪夠沈堡?虛函數(shù)表是編譯器負(fù)責(zé)維護(hù)和實現(xiàn)的,程序員并不能直接的操作和更改(其實是可以的桑李,畢竟我們知道了對象的首段地址就是基地址表,我們可以通過更改這個表來實現(xiàn),但是這些都不是C++設(shè)計者的本意踱蛀,且這樣的代碼寫出來可讀性性極差窿给,更別說什么維護(hù)了)。
這就牽扯出本文將提及的終極大殺器(蘋果大法好,不過我還是喜歡巨硬率拒。)object c 被稱作動態(tài)語言崩泡,這個動態(tài)該如何解釋呢?還是用c來做對比猬膨。c中每一個標(biāo)識符(變量)都必須有一個類型角撞,基本類型 int char float 等等,自定義類型 struct勃痴。這個類型一經(jīng)聲明谒所,其實該對象在內(nèi)存中的空間模型也就固定了(內(nèi)存空間模型包括所占字節(jié)的大小,以及自定義結(jié)構(gòu)體中沛申,各個字段的內(nèi)存區(qū)域分配)劣领。object c 不是這樣的語言。id object = *(賦值號右邊的對象是什么類型就是什么類型铁材。先記住這句話 一會兒就能體會到)尖淘。 簡單的推測,id或許是一個指針類型,因為指針?biāo)甲止?jié)數(shù)是恒定不變的啊著觉,不管什么類型的指針都可以進(jìn)行相互賦值(將32個字節(jié)的struct 賦值給int,多出來的28個字節(jié)也沒地擱啊村生,所以說指針是程序設(shè)計中最成功的概念之一)。于是我們跳轉(zhuǎn)到id的定義看到這樣的代碼
typedef struct objc_object *id;
果不其然 id 是一個指向objc_object結(jié)構(gòu)體的指針饼丘。這時候引發(fā)了我們的好奇繼續(xù)跳轉(zhuǎn)到定義
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
看到objc_object中只有一個成員 Class類型的 isa趁桃。Class又是是什么呢
typedef struct objc_class *Class;
Class其實還是一個指針類型 指向的是objc_class結(jié)構(gòu)體,這時候我們繼續(xù)跳轉(zhuǎn)到定義 可以看到objc_class結(jié)構(gòu)體的定義
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
這就相當(dāng)有趣了,這個結(jié)構(gòu)體中的字段似乎是在描述一個'類'的信息肄鸽,比如 name('類'的名稱) version ('類'的版本號) 另外還要兩個重要的成員
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists
根據(jù)名字 卫病,一個是成員變量列表 一個是方法列表。有了這些信息贴捡,對于一個有面向?qū)ο缶幊探?jīng)驗的碼農(nóng)忽肛,這些就可以描述一個類了呀。之所以前面用單引號的'類'烂斋,是我故意要做區(qū)分屹逛。還記得剛接觸面向?qū)ο笳Z言時那種不知所云的感覺么。各種書上在描述類時經(jīng)常會提到的字眼就是 設(shè)計模板 設(shè)計圖紙汛骂。類就使我們用代碼去描述的一個抽象罕模,它會被編譯器具象為內(nèi)存中的實際對象。這時候我們就會有了這么一個思考帘瞭,對啊淑掌,我們平時定義類,類信息是由編譯器負(fù)責(zé)維護(hù)的蝶念。一旦編譯完成抛腕,內(nèi)存中關(guān)于類的很多信息就消失了(當(dāng)然也完全沒必要存在芋绸,比如類的名字)。為什么object c要大費周章的用一個結(jié)構(gòu)體的變量的去保存這些信息呢担敌?如果是從文章一開始就跟緊"計算機科學(xué)中,任何問題都可以通過增加一層間接尋址來實現(xiàn)"這一思路的讀者摔敛,此時應(yīng)該一眼洞穿apple的用意。動態(tài)啊全封,動態(tài)马昙。每一個對象有一個指向它所屬'類'的指針,這個'類'實際上是存在于內(nèi)存中保存著類信息的結(jié)構(gòu)體變量,不同于以往的類信息只是編譯前安安靜靜的躺在編輯器中刹悴。有了這個'類'結(jié)構(gòu)體變量行楞,我們就真的可以為所欲為了。想想都美妙土匀,本來一旦定義了一個類律姨,類的聲明代碼在編譯前一經(jīng)確定影锈,編譯后就沒它啥事了〗杂洌現(xiàn)在是有了一個結(jié)構(gòu)體保存這些信息怀薛,每個對象都有一個指向所屬'類'的指針。我們一旦改變了結(jié)構(gòu)體中的內(nèi)容钓丰,是不是就改變了對象的'類'呢。結(jié)構(gòu)體中有方法列表每币,我們給這個列表添加一個表象携丁,是不是就等于為對象的'類'動態(tài)(因為這一切都是運行時能夠進(jìn)行的)的添加了一個方法呢。
現(xiàn)在我們就對了oc的動態(tài)性有了直觀的感受,有了'類'結(jié)構(gòu)體兰怠。我們看到的以下代碼
NSUInteger lenght = [str length];
就不再是普通的函數(shù)調(diào)用了梦鉴, 我們稱之為消息發(fā)送。str 是這個消息的接受者揭保。實際上以上的代碼會被編譯為
objc_msgSend(str, @selector(length));
這里肥橙,利用runtime為我們提供的 objc_msgSend函數(shù),我們向str發(fā)送了一條消息,通過str中的isa指針找到'類'結(jié)構(gòu)體秸侣,在結(jié)構(gòu)體的方法列表中尋找到相應(yīng)的函數(shù)實現(xiàn)存筏,然后調(diào)用。事實上味榛,如果該'類'的方法列表中沒有搜尋到椭坚,還會繼續(xù)的向父'類'去搜尋,請大家注意到Class _Nullable super_class OBJC2_UNAVAILABLE ,super_class 這個成員變量 它也是指向'類'結(jié)構(gòu)體的指針搏色,它指向的是當(dāng)前對象所屬'類'的父'類' 善茎。總之消息就這樣在這些繼承關(guān)系的'類'中傳遞频轿,直到找到合適的處理方法垂涯,或者最終也沒有找到烁焙,跑出異常。其實有了將類的信息儲存于內(nèi)存中耕赘,實現(xiàn)了一層間接尋址的概念骄蝇,其他的都是水到渠成的。文章并未提到元類的概念鞠苟,請讀者自行搜索別的文章讀乞榨,有了這篇文章做鋪墊,讀起來也定會很輕松当娱。