一.從objc_msgSend開始說
[receiver message]
//會被編譯器轉化為
objc_msgSend(receiver, selector)
所以當我們調用一個方法時,會執行的過程大致如下:
1.rutime系統會把方法調用轉化為消息發送,并且把方法的調用者,和方法選擇器,當做參數傳遞過去。
2.方法的調用者會通過isa 指針來找到所屬的類,然后在 cache 或者 methodLists 中查找該方法,找得到就跳到對應的方法去執行。
3.如果在類中沒找到該方法,則通過super_class 往上一級超類查找。如果一直找到 NSObject 都沒有找到該方法的話,可能就會觸發到消息轉發。
二. runtime 的術語的數據結構
上面講的是執行過程里面有一些術語接下來大概介紹下。
1.SEL
selector 是方法選擇器,其實作用就和名字一樣,日常生活中,我們通過人名辨別誰是誰。
Objective-C在編譯的時候,會根據方法的名字,生成一個用 來區分這個方法的唯一的一個ID,這個ID就是SEL類型的。我們需要注意的是,只要方法的名字相同,那么它們的ID都是相同的。就是說,不管是超類還是子類,不管是有沒有超類和子類的關系,只要名字相同那么ID就是一樣的。
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
這樣的函數則被認為是一種編譯錯誤,而這最終導致了一個非常非常奇怪的Objective-C特色的函數命名:
-(void)setWidthIntValue:(int)width;
-(void)setWidthDoubleValue:(double)width;
本質上,SEL只是一個指向方法的指針,它的存在只是為了加快方法的查詢速度。
2.Class
Class 其實是指向 objc_class 結構體的指針
3.Method
Method 代表類中某個方法的類型
4.Ivar
Ivar 是表示成員變量的類型。
5.IMP
它就是一個函數指針,這是由編譯器生成的。當你發起一個 ObjC 消息之后,最終它會執行的那段代碼,就是由這個函數指針指定的。而 IMP 這個函數指針就指向了這個方法的實現。
如果得到了執行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執行方法,這在后面 Cache 中會提到。
你會發現 IMP 指向的方法與 objc_msgSend 函數類型相同,參數都包含 id 和 SEL 類型。每個方法名都對應一個 SEL 類型的方法選擇器,而每個實例對象中的 SEL 對應的方法實現肯定是唯一的,通過一組 id和 SEL 參數就能確定唯一的方法實現地址。
而一個確定的方法也只有唯一的一組 id 和 SEL 參數。
6.Cache
Cache 為方法調用的性能進行優化,每當實例對象接收到一個消息時,它不會直接在 isa 指針指向的類的方法列表中遍歷查找能夠響應的方法,因為每次都要查找效率太低了,而是優先在 Cache 中查找。
Runtime 系統會把被調用的方法存到 Cache 中,如果一個方法被調用,那么它有可能今后還會被調用,下次查找的時候就會效率更高。就像計算機組成原理中 CPU 繞過主存先訪問 Cache 一樣。