先來看看我們平時開發中常常遇到的場景:

1、 作爲iOS開發的同學們應該都很熟悉字典轉對象。而字典自動轉對象減少了我們開發過程中很大的一部分工作量。而底層原理是什麼呢?又是怎麼實現的呢?

2、 我用到了別人庫,但是我需要在其他人庫里加些標記,而我又沒辦法改裏邊的代碼,我要怎麼爲庫裏的對象增加屬性呢?

3、 想要在系統的方法中增加一些我們自己的邏輯?比如我們要做頁面埋點,可是如果每個頁面都都寫的話代碼工作量大,又容易有遺漏,有沒有簡單的方法呢?

看完問題,大家可能已經想到了我們文章的重點------Runtime。

字典轉對象

1、 代碼中我們看到先創建了一個id類型的對象obj。

2、 通過Runtime中class_copyIvarList方法獲取類中的所有的屬性,下圖爲Runtime中該方法的實現,我們一起來分析下。

我們在Runtime的數據結構詳解中講到過objc_class的數據結構,objc_class中有一個ivars屬性是objc_ivar_list類型該類型的定義如下圖,我們可以結合下圖的定義來看到,cls->ivars->ivar_count可以拿到類中成員變量的數量,由獲取到objc_ivar放到了result數組裏。這就是Runtime爲我們提供的獲取一個類中所有的成員變量的方法。

3、 獲取到所有成員變量後,進行遍歷,通過ivar_getName(Ivar ivar)讀取該屬性的名字,在Runtime中Ivar是一個ivar_t的結構體,下圖爲ivar_t在Runtime中的實現,我們可以看到有個name的屬性。我們再看下ivar_getName在Runtime中的實現,讀取了ivar中的name屬性,成功的拿到了屬性的名字。

4、 由於我們取出來的屬性名字前有下劃線,所以我們先將_去掉,然後爲屬性賦值。

當然我這個代碼是最簡單的json轉model,而在我們要真正的設計一個json轉model的庫時我們需要考慮更多的問題。比如裏邊如果是數組對象,我們要怎麼處理,裏邊對應的是有一個model,我們如果如何去轉,服務端返回字段與我們不一致我們怎麼去轉等。本文主要講runtime,就不對這部分做詳細的解釋了,有興趣的同學可以看下YYModel的源碼。

關聯對象

要爲已有庫或者系統的庫中的類做擴展,我們經常會用Category來實現,而我們還想要爲這些類加一些屬性值,那我們就要用到關聯對象了。我們先來看下關聯對象的三個方法:

object: 代表傳入關聯對象的所屬對象,也就是說是增加成員的實例變量,一般傳入self。

key:標記符,一般使用selector value:傳入關聯對象

policy:關聯策略,是一個objc的枚舉

我們來詳細看下runtime如何實現的關聯對象即關聯對象實現的原理

我們首先來看下_object_set_associative_reference(id object, void *key, id value, uintptr_t policy)

通過上邊的源碼可知,objc_setAssociatedObject 方法調用了_object_set_associative_reference方法。

下面我們看下具體的方法的實現:

我們首先看一下關聯對象的數據結構,我們可以看到關聯對象的本質是runtime爲我們維護了一個全局的AssociationsManager。

在AssociationsManager中存儲AssociationsHashMap。

這個AssociationsHashMap維護了我們程序中所有的關聯對象,及關聯對象的屬性值。如下圖所示:

其實就是runtime爲我們維護了一個關聯對象存儲的哈希表,又爲我們提供了一個哈希表的管理類AssociationsManager。

下面我們看下源碼中具體的實現,我們可以看到首先爲我們的變量進行了內存管理,如果爲retain則引用計數+1,如果爲copy則爲我們執行復制。

如果傳入的value存在,那麼runtime則先從哈希表找是否已經存在object的表,如果已經存在則找到這個關聯的表,然後看錶裏是否已經存在我們傳入的key,如果key存在,那我們替換了其值,如果key不存在,我們就將對應的key與對應的值及其屬性的內存管理策略保存到AssociationsHashMap這個哈希表中。

如果目前的哈希表中找不到該對象的關聯對象的存儲,則爲改對象增加關聯對象的存儲。

以上爲我們調用objc_setAssociatedObject方法後,runtime如何爲我們存儲關聯對象的過程。

理解了關聯對象底層的數據結構及_object_set_associative_reference方法的的實現詳細大家已經猜到_object_get_associative_reference及_object_remove_assocations的實現,我這就不做過多的解釋,給大家附上源碼實現有問題可以留言交流。

iOS方法交換實現

對於第三個問題相信大家已經猜到了我們iOS開發中的黑魔法Method Swizzling和class_replaceMethod。

平時開發中我們可能會有很多場景都會去hook系統的方法,來做一些我們需要的事情,比如文章開始提到的埋點,當然如果確定我們所有的VC都繼承於同一個VC的話我們可以簡單的在BaseVC的viewDidLoad方法中,通過runtime提供的object_getClassName方法直接拿到當前類名做埋點。

但是如果我們並沒有BaseVC的,我們就只能使用iOS的黑魔法方法交換。下面我們重點分析下iOS方法交換的實現。

上邊代碼我們爲UIViewController實現了一個分類,在+load方法中我們實現了方法交換,之後我們每個VC調用到viewDidAppear方法都會執行到我們的swizzled_viewDidAppear中。

class_getInstanceMethod方法拿到Method的對象,我們主要看下method_exchangeImplementations的實現。

在之前Runtime數據結構詳解中我們講過method_t,而這的Method其實就是method_t,每個函數中都有個函數體imp,我們可以看到,一個很簡單的交換,將m1及m2的函數體做了交換,然後清了緩存。

最後更新了RR/AWZ位。

以上爲runtime在我們項目開發過程中的使用實例。

參考文章鏈接

Runtime源碼編譯鏈接:

https://mp.weixin.qq.com/s/hfwZiKt4D46gg9GOg1ZOsw

https://www.jianshu.com/p/d4176577ccd1

https://www.cnblogs.com/DafaRan/p/7878226.html

相關文章