1. 什么是Runtime機(jī)制
? ? ?Runtime[1]是一套比較底層的C語(yǔ)言庫(kù), 由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成碾阁,包含了很多底層的C語(yǔ)言API。它主要是完成了Objective-C (OC)運(yùn)行時(shí)的兩件事:類(lèi)的封裝和消息傳遞。
? ? ?本文從1. 類(lèi)的封裝管理 2.消息傳遞?總結(jié)在我學(xué)習(xí)中對(duì)Runtime的認(rèn)識(shí)坪创,隨后總結(jié)我所能理解到的Runtime機(jī)制的優(yōu)劣只祠。
2. 類(lèi)的封裝
? ? ?由于Runtime主要是用C語(yǔ)言來(lái)實(shí)現(xiàn),因此可以看到Runtime.h中蝎毡,大量使用了struct來(lái)描述對(duì)象和類(lèi)厚柳,而也使用了許多方法來(lái)封裝函數(shù)和結(jié)構(gòu)體,這使得OC在運(yùn)行中具有了面向?qū)ο蟮奶匦浴?/p>
? ? ?首先沐兵,OC的實(shí)例(Instance)别垮,類(lèi)(Class)以及相對(duì)應(yīng)元類(lèi)(metaClass)的繼承體系如圖
????類(lèi)的實(shí)例通過(guò)isa指針指向這個(gè)實(shí)例的Class,這個(gè)對(duì)象的Class中的isa指針則指向這個(gè)類(lèi)的metaClass扎谎。當(dāng)我們向一個(gè)實(shí)例發(fā)送消息碳想,runtime會(huì)在這個(gè)實(shí)例isa指向的Class方法列表中查找方法烧董;而向一個(gè)類(lèi)發(fā)送消息時(shí),會(huì)在該類(lèi)的metaClass的方法列表中進(jìn)行查找胧奔。
? ? ?在這個(gè)繼承體系的實(shí)現(xiàn)中逊移,Runtime通過(guò)大量以class_,objc_為前綴的方法(例如下面的兩個(gè)方法)來(lái)封裝結(jié)構(gòu)體和函數(shù)。使得使用者可以直接操作Class龙填,添加方法胳泉,協(xié)議等,也可以操作成員變量和屬性岩遗。
? ??在此基礎(chǔ)上扇商,Runtime所實(shí)現(xiàn)最強(qiáng)大的功能是支持在程序運(yùn)行的時(shí)候創(chuàng)建和修改類(lèi),實(shí)例操作函數(shù)[2]和運(yùn)行時(shí)創(chuàng)建關(guān)聯(lián)對(duì)象(Associated Object)宿礁。 例如:當(dāng)我們想為Category增加成員變量時(shí)案铺,可以利用以下兩個(gè)函數(shù),在運(yùn)行時(shí)動(dòng)態(tài)地增加成員變量:
? ??void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
? ? id objc_getAssociatedObject(id object, const void *key)
3. 消息傳遞
? ? ?OC除了具有靜態(tài)語(yǔ)言的特征之外梆靖,也具有動(dòng)態(tài)語(yǔ)言的特性:動(dòng)態(tài)類(lèi)型(Dynamic typing),動(dòng)態(tài)綁定(Dynamic binding)和動(dòng)態(tài)加載(Dynamic loading)控汉。Runtime機(jī)制使得這三個(gè)特性得以實(shí)現(xiàn),讓程序可以在運(yùn)行時(shí)才判斷其該有的行為涤姊,而不是像靜態(tài)語(yǔ)言一樣在編譯時(shí)就確定了行為暇番。
? ? ?一個(gè)OC程序在運(yùn)行時(shí),除了在上文第2部分提到的思喊,還主要在以下兩個(gè)場(chǎng)景中運(yùn)用了Runtime機(jī)制:結(jié)合動(dòng)態(tài)綁定的類(lèi)實(shí)現(xiàn)壁酬,結(jié)合動(dòng)態(tài)類(lèi)型的NSObject方法。三個(gè)方面互有關(guān)聯(lián)恨课,也有不同舆乔,流程如下:
3.1 結(jié)合動(dòng)態(tài)綁定的類(lèi)實(shí)現(xiàn)
首先,一個(gè)OC程序在調(diào)用一個(gè)方法時(shí)的流程如下:? ?
發(fā)送消息 -> 動(dòng)態(tài)方法決議機(jī)制 -> 消息轉(zhuǎn)發(fā)
步驟一:?發(fā)送消息(Message):若成功則調(diào)用方法剂公,若失敗且實(shí)現(xiàn)了動(dòng)態(tài)方法決議機(jī)制則進(jìn)入步驟二
步驟二:動(dòng)態(tài)方法決議機(jī)制(Dynamic Method Resolution): 若成功則調(diào)用方法希俩,若失敗且實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制則進(jìn)入Step 3
步驟三:消息轉(zhuǎn)發(fā)(Messaging Forwarding): 如提供了消息轉(zhuǎn)發(fā),則不會(huì)有錯(cuò)誤提示纲辽。不提供則報(bào)錯(cuò)
三個(gè)步驟執(zhí)行是有先后順序且不相關(guān)颜武。下面通過(guò)順序分析三個(gè)步驟來(lái)解釋Runtime在其中的應(yīng)用:
3.1.1 三個(gè)步驟完成調(diào)用方法
Step 1: 消息傳遞(Message)
? ? ?OC程序運(yùn)行時(shí)依賴(lài)消息傳遞來(lái)實(shí)現(xiàn)動(dòng)態(tài)綁定的方法調(diào)用:當(dāng)我們需要調(diào)用一個(gè)實(shí)例的方法,程序所做的是在運(yùn)行時(shí)才確定向某個(gè)類(lèi)的receiver發(fā)送消息拖吼,receiver在收到消息后鳞上,從自身的實(shí)現(xiàn)中通過(guò)@selector/SEL去尋找響應(yīng)這條消息對(duì)應(yīng)的IMP。
? ??//默認(rèn)帶有兩個(gè)隱形的參數(shù) 吊档,若有參數(shù)則為objc_msgSend(receiver, selector, arg1, ...)
?????[receiver messasge] -> objc_msgSend(receiver, selector)
? ? ?在上面的轉(zhuǎn)化中篙议,objc_msgSend會(huì)通過(guò)receiver的isa指針找到receiver對(duì)應(yīng)的類(lèi),再在該類(lèi)或該類(lèi)的父類(lèi)(沿著繼承體系繼續(xù)向上查找,如圖3.1)中的struct objc_cache *cache 和 struct objc_method_list ** methodLists尋找到所需方法鬼贱。[3]
????在objc_msgSend函數(shù)中移怯。首先通過(guò)obj的isa指針找到obj對(duì)應(yīng)的class。在Class中先去cache中 通過(guò)SEL查找對(duì)應(yīng)函數(shù)method(猜測(cè)cache中method列表是以SEL為key通過(guò)hash表來(lái)存儲(chǔ)的这难,這樣能提高函數(shù)查找速度)
????若 cache中未找到舟误。再去methodList中查找,若methodlist中未找到雁佳,則取superClass中查找脐帝。若能找到同云,則將method加 入到cache中糖权,以方便下次查找,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行炸站。
Step 2: 動(dòng)態(tài)方法決議機(jī)制(Dynamic Method Resolution)
? ? ?這個(gè)機(jī)制是在當(dāng)在子類(lèi)到父類(lèi)都尋找不到對(duì)應(yīng)的方法時(shí)星澳,讓我們能夠在運(yùn)行時(shí)動(dòng)態(tài)地為一個(gè)selector提供實(shí)現(xiàn)。依賴(lài)NSObject.h中的如下方法為類(lèi)或?qū)嵗黾有碌念?lèi)方法或?qū)嵗椒ā?/p>
? ? + (BOOL)resolveClassMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
? ? + (BOOL)resolveInstanceMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
Step 3: 消息轉(zhuǎn)發(fā)(Messaging Forwarding)
? ? ?Runtime會(huì)把消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中旱易,再給receiver最后一次機(jī)會(huì)禁偎,令其設(shè)法解決當(dāng)前還未處理的這條消息。
? ? ?Runtime會(huì)通過(guò)回調(diào)一個(gè)類(lèi)方法來(lái)尋求動(dòng)態(tài)添加方法的支持阀坏。如果receiver仍然無(wú)法正常響應(yīng)如暖,則Runtime會(huì)繼續(xù)向receiver詢(xún)問(wèn)是否有其它對(duì)象可以處理這條消息,若返回能夠處理的對(duì)象忌堂,Runtime會(huì)把消息轉(zhuǎn)給返回的對(duì)象盒至,消息轉(zhuǎn)發(fā)流程也就結(jié)束。
3.1.2 利用Method Implementations(IMP)直接調(diào)用方法實(shí)現(xiàn)
? ? ?之前在3.1.1中提到了消息傳遞過(guò)程中通過(guò)@selector/SEL去查找消息對(duì)應(yīng)的IMP士修。而我們也可以通過(guò)IMP省去Runtime消息傳遞過(guò)程中的查找操作枷遂。
? ? ?先看看Runtime.h中可以看到關(guān)于一個(gè)方法的結(jié)構(gòu)定義如下,這個(gè)結(jié)構(gòu)實(shí)際上完成了SEL和對(duì)應(yīng)IMP的一個(gè)映射
? ?? struct objc_method {
? ? ? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? ? ? char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? ? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? }
????method_types:char指針棋嘲,存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型酒唉。
? ? ?SEL:一個(gè)Int類(lèi)型的一個(gè)地址,地址中存放著方法的名字沸移,它僅僅和方法名相關(guān)痪伦。因此不同類(lèi)中可能存在擁有相同SEL的方法。工程中所有的SEL形成了一個(gè)Set雹锣。我的理解這個(gè)Set的存在是為了提高方法的查詢(xún)速度网沾。
? ? ?IMP:它本質(zhì)可以說(shuō)是一個(gè)函數(shù)指針,即方法代碼的入口點(diǎn)笆制,定義如下:
? ?? id (*IMP)(id, SEL, ...)
????因此IMP也可以在當(dāng)一個(gè)消息要被發(fā)送給某個(gè)對(duì)象很多次的時(shí)候直接調(diào)用(例如下例在for循環(huán)中調(diào)用多次setFill:绅这,直接使用NSObject類(lèi)中的methodForSelector:可以獲得一個(gè)指向方法實(shí)現(xiàn)的指針來(lái)進(jìn)行優(yōu)化)。 這樣省去了Runtime消息傳遞時(shí)的查找操作在辆,會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些证薇。
3.2 結(jié)合動(dòng)態(tài)類(lèi)型的NSObject方法
? ? ?由于大部分的對(duì)象都是由NSObject繼承而來(lái)度苔。因此也繼承了了NSObject的屬性和內(nèi)存分配方法(如下),這些方法均用于在運(yùn)行時(shí)取得信息的方法浑度。
?- (BOOL)isKindOfClass:(Class)aClass;
? - (BOOL)isMemberOfClass:(Class)aClass;
?- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
動(dòng)態(tài)特性的靈活使得id類(lèi)型在Protocol-Delegate的實(shí)現(xiàn)中也大量被使用寇窑,主要是把delegate指針類(lèi)型定義為id,從而使得運(yùn)行時(shí)實(shí)現(xiàn)動(dòng)態(tài)替換箩张。
4. Runtime的應(yīng)用
4.1 動(dòng)態(tài)添加一個(gè)類(lèi)(KVO的能夠?qū)崿F(xiàn)也依賴(lài)于這個(gè)特性)
4.2 字典轉(zhuǎn)模型 & 自動(dòng)歸檔解檔?
4.3 動(dòng)態(tài)交換類(lèi)方法/對(duì)象方法/系統(tǒng)方法
5. Runtime機(jī)制的優(yōu)劣
優(yōu)點(diǎn):更加靈活甩骏。
? ? ?Runtime環(huán)境注冊(cè)所有全局的類(lèi),函數(shù)先慷,變量等等信息等等饮笛,我們可以無(wú)限的為這個(gè)層增加必要的功能,寫(xiě)代碼時(shí)更具靈活性论熙。調(diào)用函數(shù)時(shí)候福青,會(huì)先從這個(gè)運(yùn)行時(shí)環(huán)境里檢測(cè)所以所有的可能,而并不是JMP到一個(gè)非法地址就一定會(huì)Crash脓诡。這就極大增加了程序的靈活性无午。
缺點(diǎn):增加損耗。
? ? ?很顯然Runtime的機(jī)制帶來(lái)了CPU計(jì)算的損耗祝谚。而也可以看出Runtime機(jī)制為了提高效率也設(shè)計(jì)了SEL, objc_cache *cache來(lái)緩存使用過(guò)的selector及對(duì)應(yīng)的方法的地址等方法宪迟,從而提高效率。
5. Reference
[1] AppleRuntime Guide
[2] Objective-C Runtime 運(yùn)行時(shí)之一:類(lèi)與對(duì)象
[3] Objective-C總Runtime的那點(diǎn)事兒
[4] 深入學(xué)習(xí)Objective-C語(yǔ)言的動(dòng)態(tài)特性:
[5] Understanding the Objective-C Runtime
[6] 刨根問(wèn)題Objective-C Runtime
[7] Objective-C 與 Runtime:為什么是這樣交惯?
http://www.reibang.com/p/ab966e8a82e2