一、Objective-C之Runtime的概念
前一篇關于NSProxy代理涉及到的關于消息轉發,把以前寫的runtime文章從github上轉移過來。一共三篇,似乎自己也忘記了一些runtime的細節,需要溫故一下。
一、什么是Objc的Runtime?
Runtime是Objc語言的磐石,Objc語言得以運行,也是依靠runtime庫的支持。
Objc語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在于:我們寫代碼時能夠更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。這種特性意味著Objc不僅需要一個編譯器,還需要一個運行時系統來執行編譯的代碼。對于Objc來說,這個運行時系統就像一個操作系統一樣:它讓所有的工作可以正常的運行,這個運行時系統即Objc Runtime。
Runtime基本上是用C和匯編寫的,這個庫使得C語言有了面向對象的能力。在這個庫中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,再加上了一些額外的特性。這些結構體和函數被runtime函數封裝后,我們就可以在程序運行時創建,檢查,修改類、對象和它們的方法了。runtime可以有效的幫助我們為程序增加很多動態的行為。
二、Runtime中的重要概念
先看一句最常見的方法調用代碼:[receiver message];
剛開始學習Objc時,覺得上面的方法就是調用receiver對象的message方法。這樣的理解也沒錯。更底層的原理應該是這樣的:
向receiver對象發送消息,上面的代碼會被編譯器轉成:
objc_msgSend(receiver, selector)
如果含有多個參數則是:
objc_msgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者receiver能夠找到對應的selector,那就執行接收者receiver的這個message方法。否則消息將被轉發,或臨時動態的向接收者receiver添加這個selector的實現,或者直接崩潰。
至此,知道了編譯階段只是知道了向receiver對象發送了這樣一個消息,至于消息能否被響應,需要等到運行時的具體情況決定。由此看出Objc的Runtime鑄就了它動態語言的特性。
大部分情況下你就只管寫你的Objc代碼就行,runtime系統自動在幕后辛勤勞作著。消息的執行會使用到一些編譯器為實現動態語言特性而創建的數據結構和函數,Objc中的類、方法和協議等在runtime中都由一些數據結構來定義。
三、梳理objc_msgSend
方法原型是:id objc_msgSend(id self, SEL op, ...)。
id
id是指向類實例的結構體指針。定義如下:
typedef struct objc_object *id;
id這個結構體的定義本身就帶了一個*號, 所以我們在使用其他NSObject類型的實例時需要在前面加上*, 而使用id時卻不用。
接著,我們發現一個objc_object的定義,繼續深入進去看下..
objc_object
objc_object其實是一個結構體,結構體里包含一個指向Class類的isa指針。根據isa指針就可以找到id實例所屬的Class類了。
struct objc_object { Class isa; };
發現一個Class的定義,繼續深入進去看下..
Class
Class是一個指針類型,指向了objc_class類型。
typedef struct objc_class *Class;
發現一個objc_class的定義,繼續深入進去看下..
objc_class
這個objc_class可是一個重要大明星。也是runtime的重點。到這里,我們可以得出一個結論:每個id對象都有一個指向所屬類的指針isa。通過該指針,對象可以找到它所屬的類,也可以找到了其全部父類(這一句需要在學完objc_class結構體后得到,不過本篇不打算再說objc_class了,因為篇幅有限,重點需要另開一篇來學習總結)。
花開兩朵各表一枝,這條線先挖到objc_class這里。然后回頭繼續看objc_msgSend的第二個參數SEL類型的op。
SEL
SEL其實是selector方法選擇器的標示,換言之:SEL是用來標示selector的。
typedef struct objc_selector *SEL;
Objc在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL。如下代碼所示:
SEL sel = @selector(method); NSLog(@"sel: %p", sel);//output:sel: 0x108dfbba3兩個類之間,不管它們是父類與子類的關系,還是之間沒有這種關系,只要方法名相同,那么它的SEL就是一樣的。每一個方法都對應著一個SEL。編譯器會根據每個方法的方法名為那個方法生成唯一的SEL。這些SEL組成了一個Set集合,當我們在這個集合中查找某個方法時,只需要去找這個方法對應的SEL即可。而SEL本質是一個字符串,所以直接比較它們的地址即可。
當然,不同的類可以擁有相同的selector。不同類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。
objc_msgSend的調用過程(先理解其中的概念):
當向一個對象發送消息時,objc_msgSend方法根據對象的isa指針找到對象的類,然后在類的調度表(dispatch table)中查找selector。如果無法找到selector,objc_msgSend通過指向父類的指針找到父類,并在父類的調度表(dispatch table)中查找selector,以此類推直到NSObject類。一旦查找到selector,objc_msgSend方法根據調度表的內存地址調用該實現。 通過這種方式,message與方法的真正實現在執行階段才綁定。
為了保證消息發送與執行的效率,系統會將全部selector和使用過的方法的內存地址緩存起來。每個類都有一個獨立的緩存,緩存包含有當前類自己的 selector以及繼承自父類的selector。查找調度表(dispatch table)前,消息發送系統首先檢查receiver對象的緩存。
上面的描述如下圖所示:
至此初步認識了runtime,也初步了解了Objc的重要概念內容,比如id、SEL等。當然更深的東西,還要繼續學習挖掘~~~
轉載于:https://www.cnblogs.com/vokie/p/9282801.html
總結
以上是生活随笔為你收集整理的一、Objective-C之Runtime的概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国内油价本周三“四连涨&rd
- 下一篇: [P1580] yyy loves Ea