事岀無常必有妖-iOS捉妖記之(Runtime)

發表於2016-12-28
111644823-5eb0eb67175ba6af
捉妖記.jpg

寫在前面

看完本篇之後你將獲得:

  • 瞭解什麼是runtime
  • 知道可以利用runtime做到哪些事情
  • 掌握用runtime開發的常用方法

Runtime是開源的,任何時候你都可以從http://opensource.apple.com獲取。事實上檢視 Objective-C 原始碼是我理解它是如何工作的第一種方式,在某些問題上要比讀蘋果的文件要好。你也可以下載筆者在寫這篇文章時最新objc4-680.tar.gz

引言

相信很多從事iOS開發的小夥伴們都聽過這樣一句形容runtime的話:

runtime就像是iOS開發中的妖怪,誰都聽說過,但少有人見(用)到過!

這句話是某知名培訓機構內某老師對學生們說的一句話(原諒我每年都會去down培訓視訊大致的過一遍),相信不少人尤其是初學的萌新們還沒了解過runtime,聽了這句話就被嚇到了!直接在心裡給runtime打個一打標籤[危險,慎用,底層,難,用不到,不用掌握]。以至於很多人做了有一段時間的iOS開發卻依然對其一知半解……

定義

Objective-C 的 Runtime 是一個執行時庫(Runtime Library),它是一個主要使用 C 和彙編寫的庫,為 C 新增了面相物件的能力並創造了 Objective-C。這就是說它在類資訊(Class information) 中被載入,完成所有的方法分發,方法轉發,等等。Objective-C runtime 建立了所有需要的結構體。

其實就在下個人的理解:runtime就是丫Objective-C 的靈魂!Objective-C之所以叫Objective-C是因為他比C語言不同,是物件導向的。但是Objective-C為什麼有面相物件的能力?就是因為有runtime這個鬼東西!

進階

我們為什麼要學習runtime?

  • runtime可以遍歷物件的屬性
  • runtime可以動態新增/修改屬性,動態新增/修改/替換方法,動態新增/修改/替換協議
  • runtime可以動態建立類/物件/協議等等
  • runtime可以方法攔截呼叫

其實runtime所能做的還不止這些,你甚至可以利用它來把一個Class A的例項物件a在程式中當作Class B的例項物件來用。所以很多iOS開發者把runtime叫做obj-C的黑魔法!

常用方法

先來個最簡單最基本的也是幾乎所有runtime文必備的例子:

obj-C: [obj func];
runtime:objc_msgSend(obj, @selector(func);

很多初學者除了知道runtime把物件的方法呼叫轉化成訊息傳送的程式碼之後就不知道其他的了,但是顯然僅僅知道上述的轉化並沒有什麼“吡-”用,我們來看runtime中比較常用(實用)的幾種基本用法:

  • 遍歷物件的屬性
    首先定義一個簡單的類Person

    然後在需要遍歷物件的屬性時

    這時就會列印出這個類物件的屬性相關資訊:

    name:T@”NSString”,C,N,V_name
    age:Tq,N,V_age

  • 訊息轉發

其實講道理的話,訊息轉發不是三言兩語就可以講清的,我只能在這裡粗淺的介紹一下,讓大家會用而已。
週末可能會寫一篇詳解runtime的文章來細緻的介紹runtime中的術語以及runtime訊息傳送機制,動態方法解析,重定向以及訊息轉發。包括我們熟用但是可能不知道其原始碼是什麼樣的id,SEL,objc_object,objc_class以及其結構也會詳細的講解到,感興趣的小夥伴可以關注我,這樣我的新文章會第一時間推送給你。

我們這裡的[訊息轉發]指的就是我上面提到的動態方法解析,重定向以及訊息轉發,我們先來看一張圖:

121644823-e59e8c51c0eb393e
訊息轉發流程.png

動態方法解析:

從上圖可以知道,當對一個例項物件obj傳送一條訊息func[obj func],當前obj如果沒有對func實現對應的方法,那麼就runtime會呼叫+ (BOOL)resolveInstanceMethod:(SEL)sel方法允許開發者對當前受到的訊息func做出響應,這就是動態方法解析

繼續拿上面的Person舉例子,給Person類加一個體重weight屬性

然後在.m檔案中加入一下程式碼

然後可以在程式碼就呼叫Person的setWeight方法

這時候如果不重寫+ (BOOL)resolveInstanceMethod:(SEL)sel方法本應該異常的,但是你可以發現程式會列印出資訊:

Dynamic setWeight

重定向:

那麼還是看圖說話,如果沒有重寫+ (BOOL)resolveInstanceMethod:(SEL)sel方法,那就就會呼叫- (id)forwardingTargetForSelector:(SEL)aSelector方法,把這個訊息讓另一個物件來處理,這次叫做重定向

跟著上面的例子走,先另一個類People用來等待重定向:

給新寫的People類加一個weight方法,但是注意:People沒有weight屬性!

接下來我們重寫- (id)forwardingTargetForSelector:(SEL)aSelector方法:

然後我們在剛才的執行程式碼中:

然後執行,經歷過上面的例子你肯定知道不會異常啦,而且你會發現雖然你給weight屬性賦值明明是75,可是列印結果是:weight = 70。這就是Person類- (id)forwardingTargetForSelector:(SEL)aSelector方法中把這條資訊拋給了people物件,呼叫了People類的weight方法!

訊息轉發

那麼如果上面的兩個方法都沒有重寫,並且訊息依然是當前物件沒有實現的方法,runtime才會啟用訊息轉發呼叫– (void)forwardInvocation:(NSInvocation *)anInvocation,需要注意的是很多文章沒有提到這個方法花費代價較大,如果要實現把訊息轉發類似的功能建議最好使用重定向,而且再呼叫這個方法前runtime會先呼叫- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法。

我們跟著上面的例子,繼續給Person類加入屬性:

以及上面提到的兩個方法:

別忘記了在People類中新增對應的方法:

最後,我門只需要在執行程式碼塊中加入程式碼:

結果顯而易見,相信各位都知道將會列印資訊:

People setID: xxxx

寫在最後

其實runtime就是我們無時無刻不在用的東西,只是人們習慣對看不到的東西懷有恐懼心理而已。我們平時的obj-C程式碼都是被runtime轉譯為c和組合語言執行的。我個人認為大公司為什麼喜歡在面試時問runtime相關的東西是因為大公司往往不僅僅要會幹活的人,它還會要求這些會幹活的人知道其中的原理!我們自己也應該要求自己或多或少的理解這些原理,知道我們為什麼寫出的obj-C程式碼經歷了哪些過程run到我們的裝置上,不要敲了很多年的程式碼還是一隻只會幹活的碼農。

相關文章