導(dǎo)語
一個成熟的計算機語言必然有豐富的體系,復(fù)雜的容錯機制,處理邏輯以及判斷邏輯梳虽。但這些復(fù)雜的邏輯都是圍繞一個主線豐富和展開的,所以在學習計算機語言的時候灾茁,先掌握核心窜觉,然后了解其原理,明白程序語言設(shè)計的實質(zhì)和當時選擇這種處理方式的原因是極其必要的北专,而且也是學習語言的捷徑禀挫。
所以在學習的過程中,需要把握幾個核心
先專注主線拓颓,后豐富周邊语婴;
先宏觀了解,后微觀精通;
多設(shè)身處地思考驶睦,理解代碼設(shè)計的原因;
理解代碼設(shè)計的原理和優(yōu)化
OC中處理方法的業(yè)務(wù)邏輯和其他語言不同砰左,OC語言是動態(tài)語言(動態(tài)綁定
,動態(tài)加載
(dynamatic binding),動態(tài)類型
)场航。其中動態(tài)加載就涉及到OC的運行時菜职。在OC中,方法是動態(tài)實現(xiàn)的旗闽,調(diào)用方法實際就是在發(fā)送消息
酬核。
試想一下,一個方法的實現(xiàn)必然包含三個部分:
1.執(zhí)行方法的對象
2.方法名稱
3.不確定的參數(shù)
SEL
只是一個方法名稱适室,IMP
才是執(zhí)行方法最終的函數(shù)嫡意。IMP
是一個函數(shù)指針,包含一個接收消息的對象
id(self 指針), 調(diào)用方法的選標
SEL
(方法名),以及不定個數(shù)的參數(shù)
,并返回一個 id。也就是說 IMP
是消息最終調(diào)用的執(zhí)行代碼,是方法真正的實現(xiàn)代碼 捣辆。
提問時間到了:
- 動態(tài)和靜態(tài)有什么區(qū)別蔬螟?
- 執(zhí)行方法是怎么實現(xiàn)的?
- OC的方法和C語言的函數(shù)原理一樣么汽畴?
動態(tài)和靜態(tài)有區(qū)別的旧巾;首先我們從最表層理解耸序,一個方法的實現(xiàn)必然要包含執(zhí)行者,方法名和不確定的參數(shù)和返回值鲁猩。無論是靜態(tài)或者動態(tài)方法都必須這三個必要元素(動態(tài)和靜態(tài)的區(qū)別就在于在何時確定這些必要元素
)坎怪。
方法的執(zhí)行包含編譯和運行兩個過程。
- 靜態(tài)方法是在編譯時已經(jīng)確定了三個要素廓握,且不能更改搅窿。若類型不對,就會直接發(fā)出警告隙券。
- 而OC的動態(tài)方法可以直接跳過編譯男应,在運行時才開始添加函數(shù)調(diào)用,決定執(zhí)行方法的三個要素娱仔。這就是動態(tài)方法(至于怎么執(zhí)行沐飘,下面開始講解)
這三個元素是如何確定的呢?首先我們看一段示例代碼
Dog *aDog = [[Dog alloc]init];
[aDog run];
在執(zhí)行方法時牲迫,是怎么確定的呢耐朴?
此時我們需要注意OC內(nèi)部方法的實質(zhì):OC中,方法實現(xiàn)實質(zhì)就是發(fā)送消息恩溅。
[aDog run];
代碼的實質(zhì)就是[ objc_sendMsg]
,它會找到執(zhí)行方法的三個要素谓娃,找到就按照規(guī)則執(zhí)行脚乡。
發(fā)送消息是通過 objc_send(id, SEL, ...)
來實現(xiàn)的,它首先會在對象的類對象的 cache
,methodlist
以及父類對象的 cache
,methodlist
中依次查找 SEL
對應(yīng) 的 IMP
;
如果沒有找到且實現(xiàn)了動態(tài)方法決議機制就會進行決議滨达。
如果沒有實現(xiàn)動態(tài)方法決議機制或決議失敗且實現(xiàn)了消息轉(zhuǎn)發(fā)機制就會進入消息轉(zhuǎn)發(fā)流程,否則程序 crash奶稠。
也就是說如果同時提供了動態(tài)方法決議
和消息轉(zhuǎn)發(fā)
,那么動態(tài)方法決議先于消息轉(zhuǎn)發(fā),只有當動態(tài)方法決議依然無法正確決議 selector
的 實現(xiàn),才會嘗試進行消息轉(zhuǎn)發(fā)。當然捡遍,實際過程不可能那么簡單锌订,在開發(fā)語言之初,肯定會完善各種復(fù)雜場景和做了很多優(yōu)化画株,接下來我們一起研究下OC對方法執(zhí)行和擴展和優(yōu)化:
- 第一步:先找方法
- 第二步:動態(tài)方法決議
- 第三部:消息轉(zhuǎn)發(fā)
- 最后: 報錯
消息轉(zhuǎn)發(fā)
通常,給一個對象發(fā)送它不能處理的消息會得到出錯提示,然而,Objective-C
運行時系統(tǒng)在拋出錯誤之前, 會給消息接收對象發(fā)送一條特別的消息 forwardInvocation
來通該對象,該消息的唯一參數(shù)是個 NSInvocation
類型的對象——該對象封裝了原始的消息和消息的參數(shù)辆飘。我們可以實現(xiàn) forwardInvocation:
方法來對不能處理的消息做一些默認的處理,也可以將消息轉(zhuǎn)發(fā)給其他對 象來處理,而不拋出錯誤。
- 1,首先去該類的方法
cache
中查找,如果找到了就返回它;- 2,如果沒有找到,就去該類的方法列表中查找谓传。如果在該類的方法列表中找到了,則將
IMP
返回,并將 它加入cache
中緩存起來蜈项。根據(jù)最近使用原則,這個方法再次調(diào)用的可能性很大,緩存起來可以節(jié)省下次 調(diào)用再次查找的開銷。- 3,如果在該類的方法列表中沒找到對應(yīng)的
IMP
,在通過該類結(jié)構(gòu)中的super_class
指針在其父類結(jié)構(gòu)的方法列表中去查找,直到在某個父類的方法列表中找到對應(yīng)的IMP
,返回它,并加入cache
中;- 4,如果在自身以及所有父類的方法列表中都沒有找到對應(yīng)的
IMP
,則看是不是可以進行動態(tài)方法決議(后 面有專文講述這個話題);- 5,如果動態(tài)方法決議沒能解決問題,進入下面要講的消息轉(zhuǎn)發(fā)流程续挟。便利函數(shù):我們可以通過
NSObject
的一些方法獲取運行時信息或動態(tài)執(zhí)行一些消息:
class 返回對象的類:
isKindOfClass,isMemberOfClass 檢查對象是否在指定的類繼承體系中;
respondsToSelector 檢查對象能否相應(yīng)指定的消息;
conformsToProtocol 檢查對象是否實現(xiàn)了指定協(xié)議類的方法;
methodForSelector 返回指定方法實現(xiàn)的地址紧卒;
performSelector:withObject 執(zhí)行 SEL 所指代的方法
OC做為一門面向?qū)ο笳Z言,自然具有面向?qū)ο蟮恼Z言特性诗祸,如封裝
跑芳、繼承
轴总、多態(tài)
。他具有靜態(tài)語言的特性(如C++)博个,又有動態(tài)語言的效率(動態(tài)綁定怀樟、動態(tài)加載等)。整體來說坡倔,確實是一門不錯的編程語言漂佩。
OC的動態(tài)語言特性
現(xiàn)在,讓我來想想OC的動態(tài)語言特性罪塔。OC的動態(tài)特性表現(xiàn)為了三個方面:
動態(tài)類型
投蝉、動態(tài)綁定
、動態(tài)加載
征堪。
之所以叫做動態(tài)瘩缆,是因為必須到運行時(runtime
)才會做一些事情。
(1)動態(tài)類型
動態(tài)類型佃蚜,說簡單點就是id類型庸娱。動態(tài)類型是跟靜態(tài)類型相對的。像內(nèi)置的明確的基本類型都屬于靜態(tài)類型(int谐算、NSString等)熟尉。靜態(tài)類型在編譯的時候就能被識別出來。所以洲脂,若程序發(fā)生了類型不對應(yīng)斤儿,編譯器就會發(fā)出警告。而動態(tài)類型就編譯器編譯的時候是不能被識別的恐锦,要等到運行時(runtime
)往果,即程序運行的時候才會根據(jù)語境來識別。所以這里面就有兩個概念要分清:編譯時跟運行時一铅。
(2)動態(tài)綁定
動態(tài)綁定(dynamic binding
)貌似比較難記憶陕贮,但事實上很簡單,只需記住關(guān)鍵詞@selector/SEL
即可潘飘。先來看看“函數(shù)”肮之,對于其他一些靜態(tài)語言,比如c++,一般在編譯的時候就已經(jīng)將將要調(diào)用的函數(shù)的函數(shù)簽名都告訴編譯器了卜录。靜態(tài)的局骤,不能改變。而在OC中暴凑,其實是沒有函數(shù)的概念的峦甩,我們叫“消息機制”,所謂的函數(shù)調(diào)用就是給對象發(fā)送一條消息。這時凯傲,動態(tài)綁定的特性就來了犬辰。OC可以先跳過編譯,到運行的時候才動態(tài)地添加函數(shù)調(diào)用冰单,在運行時才決定要調(diào)用什么方法幌缝,需要傳什么參數(shù)進去。這就是動態(tài)綁定诫欠,要實現(xiàn)他就必須用SEL變量綁定一個方法涵卵。最終形成的這個SEL
變量就代表一個方法的引用。這里要注意一點:SEL
并不是C里面的函數(shù)指針荒叼,雖然很像轿偎,但真心不是函數(shù)指針。SEL
變量只是一個整數(shù)被廓,他是該方法的ID坏晦。以前的函數(shù)調(diào)用,是根據(jù)函數(shù)名嫁乘,也就是字符串去查找函數(shù)體昆婿。但現(xiàn)在,我們是根據(jù)一個ID整數(shù)來查找方法蜓斧,整數(shù)的查找字自然要比字符串的查找快得多仓蛆!所以,動態(tài)綁定的特定不僅方便挎春,而且效率更高看疙。
(3)動態(tài)加載
動態(tài)加載就是根據(jù)需求動態(tài)地加載資源。我對動態(tài)加載比較陌生搂蜓,所以就沒什么可總結(jié)的啦狼荞。等以后慢慢完善辽装。
寫在最后
我得寫作原則:
在技術(shù)學習道路上帮碰,閱讀量和代碼量絕不能線性提升你的技術(shù)水平。
同樣寫文章也是如此拾积,作者所寫的文章完全是基于自己對技術(shù)的理解殉挽,在寫作時也力求形象不抽象。絕不copy充數(shù)拓巧,所以也歡迎大家關(guān)注和參與討論斯碌。
技術(shù)學習絕不能孤膽英雄獨闖天涯,而應(yīng)在一群人的交流碰撞肛度,享受智慧火花的狂歡傻唾。
希望我的文章能成為你的盛宴,也渴望你的建議能成為我的大餐。
如有錯誤請留言指正冠骄,對文章感興趣可以關(guān)注作者不定期更新伪煤。