iOS Runtime 初識與應用

什麼是 Runtime

什麼是運行時呢?從字面意思來看,就是一個程序在其運行的過程當中所作的一些事情。而蘋果在 object—C 中提供了一套純 c 語言的 api,這套 api 即爲 runtime。objective-c

在 iOS 開發的過程當中,正式由於runtime 的特性,讓 object-C 具備了吸引人的魅力。使得咱們能夠真正作到玩語言,作出高逼格的花樣,快樂就完了~api

要了解運行時,咱們得先了解 object-C 的消息機制,能夠看下面的流程架構

一、編譯器會先將代碼 [obj doSomeThing] 轉化爲 objc_msgSend(obj, @selector (doSomeThing)) 函數去執行。函數

二、在 objc_msgSend() 函數中,首先經過 obj 的 isa 指針找到 obj 對應的 class。架構設計

三、在 class 中會先去 cache 中 經過 SEL 查找對應函數 doSomeThing(cache 中method 列表是以 SEL 爲 key 經過 hash 表來存儲的,這樣能提升函數查找速度),若 cache 中未找到。再去 class 中的消息列表 methodList 中查找,若 methodList 中未找到,則取 superClass 中查找。若能找到,則將 doSomeThing 加入到 cache中,以方便下次查找,並經過 method 中的函數指針跳轉到對應的函數中去執行。debug

看完上面的流程,可能會迷惑裏面提到的一下字端是什麼意思,那咱們來看一下一個 OC 類中都包含了什麼?設計

能夠先看類結構體 struct objc_class。雖然該結構體中有許多變量,可是從變量名中咱們能夠大概理解其含義,結構體裏包含了 指像父類的指針、類名、版本等等信息,裏面有些變量會在下面的應用中進行用到。3d

下面就讓咱們來看看 runtime 的具體應用吧~指針

Runtime 的應用

Runtime 的功能很是強大,這也是魅力所在,它可以在程序運行的時候,獲取一個類中的全部信息,而且可以根據開發者意願去修改。腦洞多大,變化就能多大~code

接下來,本文主要就下面幾點進行講解:

(1)使用 class_addMethod 函數在運行時對函數進行動態增長新函數

(2)消息轉發

(3)使用 class_copyPropertyList 及 property_getName 獲取類的屬性列表及每一個屬性的名稱

(4) 使用 class_copyMethodList 獲取類的全部方法列表

(5) Method Swizzling

動態添加方法 - class_addMethod

首先來看一下 API 文檔解釋

先看下函數中的各個參數的含義:

Class _Nullable cls:傳入的是一個類,也就是你想要在哪一個類裏進行添加方法

SEL _Nollnull name:想要添加的方法名字

IMP _Nonnull imp:imp 是 Implement 縮寫,表示指向方法的實現地址

const char * _Nonnull types:對於參數與返回值的描述。舉個例子:"v@:",表示的是返回值爲 void 而且沒有參數。

下面看一下具體的調用:

這裏要給 HJXPerson 類動態添加一個 sayHello 的方法。首先要拿到添加的方法 sayHello 的 IMP 指針,而後調用相應的接口去添加方法。

(注:當要執行動態添加方法的時候,須要用 performSelector 來進行調用,由於 performSelector 是運行時系統負責去找方法的,在編譯時候不作任何校驗;若是直接調用編譯是會自動校驗)

消息轉發

文章開頭介紹了 Objective-C 中調用一個方法的流程,但若是最後都沒有找到對應方法,咱們的程序都會 crash 並拋出信息沒有找到對應的方法。其實在 crash 以前還會進行一套流程,那就是消息轉發。轉發流程圖以下:

下面將分別結合代碼事例進行介紹這幾種方法的處理:

  • resolveInstanceMethod

此爲消息轉發的先導,也叫動態決議機制。見下面例子:

在 HJXPerson 類中並無聲明 eat 這個函數,因此在實例調用方法的時候會進入到這個回調之中。

其實,objective-c 的方法就是至少帶有兩個參數(self 和 cmd)的普通的 C 函數。所以在代碼中提供這樣一個 C 函數 dynamicMethodIMPEat,讓它來充當對象方法 eat 這個 selector 的動態實現。

由於 eat 是被對象所調用,因此它被認爲是一個對象方法,於是應該在 resolveInstanceMethod 方法中爲其提供實現。

  • forwardingTargetForSelector

這個方法只能讓咱們把消息轉發到另外一個能處理這個消息的對象,可是沒法處理消息的內容,好比參數和返回值。例子以下:

該類爲 HJXCat 類,裏面聲明瞭 「useTool」 這個方法但並無實現,因此在未添加 resolveInstanceMethod 處理的時候,會進入到 forwardingTargetForSelector 方法中,而後能夠根據傳入的 aSelector,來轉交給其餘類去處理相應的方法。

(注:返回的爲想要把方法交給的類的一個實例對象)

  • forwardInvocation

先看下工程代碼

forwardInvocation 的總體流程和 forwardingTargetForSelector 基本上是差很少的。經過 aSelector 來進行判斷是否要進行轉發,而後進行手動簽名。而後在 anInvocation 中能夠獲取到函數傳裏過程當中全部信息。

forwardingTargetForSelector 和 forwardInvocation 區別

快速轉發:forwardingTargetForSelector 僅支持一個對象的返回,也就是說消息只能被轉發給一個對象、沒法處理消息的內容,好比參數和返回值。

普通轉發:forwardInvocation 能夠將消息同時轉發給任意多個對象。

消息轉發總結

  • 首先看是否爲該 selector 提供了動態方法決議機制,若是提供了則轉到 2;若是沒有提供則轉到 3;

  • 若是動態方法決議真正爲該 selector 提供了實現,那麼就調用該實現,完成消息發送流程,消息轉發就不會進行了;若是沒有提供,則轉到 3;

  • 其次看是否爲該 selector 提供了消息轉發機制,若是提供了消息了則進行消息轉發,此時,不管消息轉發是怎樣實現的,程序均不會 crash。(由於消息調用的控制權徹底交給消息轉發機制處理,即便消息轉發並無作任何事情,運行也不會有錯誤,編譯器更不會有錯誤提示。);若是沒提供消息轉發機制,則轉到 4;

  • 運行報錯:沒法識別的 selector,程序 crash

property_getName 及 class_copyMethodList

這兩個函數總從字面意思上,能夠看出分別是獲取一個類中的全部屬性名稱及方法名。這兩個方法能夠算是基礎,讓你能夠知道一個類內的全部屬性及方法,以便你接下來能夠爲所欲爲的對於其修改,修改哪裏。

下面爲調用方法來獲取 HJXCat 中的屬性及方法:

(上面的各個屬性都是聲明在 .m 文件中的)

iOS 黑魔法 - Method Swizzling

首先讓咱們來看下方法交換的原理:

  • 在 Objective-C 中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是 selector 的名字。利用 Objective-C 的動態特性,能夠實如今運行時偷換 selector 對應的方法實現,達到給方法掛鉤的目的。

  • 每一個類都有一個方法列表,存放着 selector 的名字和方法實現的映射關係。IMP 有點相似函數指針,指向具體的 Method 實現。

歸根結底,都是偷換了 selector 的 IMP

跟消息轉發相比,Method Swizzling 的作法更爲隱蔽,甚至有些冒險,也增大了debug的難度。

下面咱們來看一下代碼部分:

就這麼簡簡單單幾行,就可以實現方法的交換。原有的 eat 方法在調用 hasEatenFull 方法後,就與 play 方法進行了交換,再次執行 eat 方法,其實就是調用了 play 方法。

雖然 Swizzling 能夠任你喜歡的去弄,想要玩起來,anyway,均可以。可是開發工程中,咱們須要先好好的去想想,能不能利用良好的代碼和架構設計來實現,或者是深刻語言的特性來實現。好用,可是不能濫用。

後記

Runtime 在代碼運行的時候,最大提供給咱們操做性,能夠幫助咱們理解代碼的運行,窺探咱們看不到的代碼,擴展更多更有趣的功能。

代碼玩起來,快樂就完啦~用好,不濫用~

Article by 夏風_Me

相關文章
相關標籤/搜索