使用objc runtime實現綠色的懶加載

地址:AutoPropertyCocoagit

文章所指的懶加載形式一般以下github

- (id)lazyloadProperty{

    if(_lazyloadProperty == nil){

        _lazyloadProperty = [XClass ...];
    }
    return _lazyloadProperty;
}複製代碼

通常使用宏定義能夠輕鬆完成。可是沒有一致性,移植差。 利用objc runtime的動態性實現懶加載能夠實現便可增長又可刪除功能,能夠針對單個實例進行修改,避免污染類型。該三方彌補了目前沒有閉環實現懶加載三方的空缺。數組

主要實現:安全

  1. 實例或者類的懶加載
  2.  若是是實例對象則鉤住並修改類型將其子類化
  3. 對該類型進行method swizzling 若是如今進行解綁,則判斷是不是本身實現的方法.
  4. 若是是本身實現的方法->5,不然->6
  5. 調用method swizzling
  6. 還原 刪除這個類型的這個方法

        主要難點和有價值的內容 :框架

  1. 本身實現method swizzling
  2. 從新實現objc1時代的方法class_removeMethods
  3. 鉤住運行時中的runtimelock,實現修改類型數據時的安全性

咱們在實現method swizzling時的兩個API函數

OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);複製代碼

無論使用哪一種,若是這個類型沒有實現該方法而是父類實現的話,就須要動態增長一個方法。動態增長的方法在Objc1時代,是能夠經過下列方法刪除的:ui

OBJC_EXPORT void
class_removeMethods(Class _Nullable, struct objc_method_list * _Nonnull)
    OBJC2_UNAVAILABLE;複製代碼

Objc2時代以後runtime被重寫後沒有該方法了,而且新的runtime的類結構看起來就沒打算讓開發者刪除方法,因此這裏將過程記下。this

首先看類讀寫器的結構class_rw_tspa

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;//`刪除這裏的一個方法`
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
};複製代碼

method_array_t繼承於list_array_tt,它是數組結構。存儲的內容是method_list_t.線程

method_list_t又繼承於entsize_list_tt,他也是數組結構。

整個method_array_t結構是二維數組。每次刪掉一個method_t須要用新method_list_t替換原對象。

550026-20190612223248657-276167664.png

而後是線程安全的問題,須要獲取到蘋果在操做類型的時候使用的讀寫鎖(pthread_rw_lock_t runtimelock)。沒有這把鎖任何對runtime的修改都是不可靠的。

最終採起的方式是:劫持暴露了符號的系統函數而後阻塞線程

550026-20190612235212546-342620278.png

系統C函數使用的是臉書的魚鉤,這個鉤子在macOS其實也是能夠正常工做的。

剩下的就是尋找合適的函數了,這函數要知足兩個條件:

  1. 該函數在符號表中存在
  2. 函數內部在lock runtimelock以後存在知足條件1的第二個函數

找了半天發現最合適的只有objc_allocateProtocol()了,objc_allocateProtocol內部會調用calloc(),因此第二個被劫持函數就是calloc。爲了減少calloc的開銷,須要稍微作一些工做。

  1. 對每次調用進行比較線程ID的操做顯然比暴力阻塞線程好。
  2.  減少劫持後的calloc的調用棧。

雖然不是什麼吸引人的UI框架仍是但願你們點個贊吧。另外,有成都的公司招iOS嗎😂?

meterwhite@outlook.com

相關文章
相關標籤/搜索