iOS Runtime詳解

1、runtime(運行時機制)是什么

  • runtime是屬于OC的底層,是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API,可以進行一些非常底層的操作(用OC是無法現(xiàn)實的, 不好實現(xiàn))。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉成了runtime的C語言代碼, runtime算是OC的幕后工作者。

  • 例如:

    // OC : 
    [[MJPerson alloc] init] 
    // runtime : 
    objc_msgSend(objc_msgSend("MJPerson" , "alloc"), "init")
    

2、runtime知識圖譜

3、runtime數(shù)據(jù)結構

  • 3.1、objc_object
struct objc_object {
   private:
       isa_t isa;
   
   public:
   
       // ISA() assumes this is NOT a tagged pointer object
       Class ISA();
   
       // getIsa() allows this to be a tagged pointer object
       Class getIsa();
   
       // initIsa() should be used to init the isa of new objects only.
       // If this object already has an isa, use changeIsa() for correctness.
       // initInstanceIsa(): objects with no custom RR/AWZ
       // initClassIsa(): class objects
       // initProtocolIsa(): protocol objects
       // initIsa(): other objects
       void initIsa(Class cls /*indexed=false*/);
       void initClassIsa(Class cls /*indexed=maybe*/);
       void initProtocolIsa(Class cls /*indexed=maybe*/);
       void initInstanceIsa(Class cls, bool hasCxxDtor);
   
       // changeIsa() should be used to change the isa of existing objects.
       // If this is a new object, use initIsa() for performance.
       Class changeIsa(Class newCls);
   
       bool hasIndexedIsa();
       bool isTaggedPointer();
       bool isClass();
   
       // object may have associated objects?
       bool hasAssociatedObjects();
       void setHasAssociatedObjects();
   
       // object may be weakly referenced?
       bool isWeaklyReferenced();
       void setWeaklyReferenced_nolock();
   
       // object may have -.cxx_destruct implementation?
       bool hasCxxDtor();
   
       // Optimized calls to retain/release methods
       id retain();
       void release();
       id autorelease();
   
       // Implementations of retain/release methods
       id rootRetain();
       bool rootRelease();
       id rootAutorelease();
       bool rootTryRetain();
       bool rootReleaseShouldDealloc();
       uintptr_t rootRetainCount();
   
       // Implementation of dealloc methods
       bool rootIsDeallocating();
       void clearDeallocating();
       void rootDealloc();
   
   private:
       void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
   
       // Slow paths for inline control
       id rootAutorelease2();
       bool overrelease_error();
   
   #if SUPPORT_NONPOINTER_ISA
       // Unified retain count manipulation for nonpointer isa
       id rootRetain(bool tryRetain, bool handleOverflow);
       bool rootRelease(bool performDealloc, bool handleUnderflow);
       id rootRetain_overflow(bool tryRetain);
       bool rootRelease_underflow(bool performDealloc);
   
       void clearDeallocating_slow();
   
       // Side table retain count overflow for nonpointer isa
       void sidetable_lock();
       void sidetable_unlock();
   
       void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
       bool sidetable_addExtraRC_nolock(size_t delta_rc);
       size_t sidetable_subExtraRC_nolock(size_t delta_rc);
       size_t sidetable_getExtraRC_nolock();
   #endif
   
       // Side-table-only retain count
       bool sidetable_isDeallocating();
       void sidetable_clearDeallocating();
   
       bool sidetable_isWeaklyReferenced();
       void sidetable_setWeaklyReferenced_nolock();
   
       id sidetable_retain();
       id sidetable_retain_slow(SideTable& table);
   
       uintptr_t sidetable_release(bool performDealloc = true);
       uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
   
       bool sidetable_tryRetain();
   
       uintptr_t sidetable_retainCount();
   #if DEBUG
       bool sidetable_present();
   #endif
};
  • 3.2、objc_class
struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;

    void setInfo(uint32_t set) {
        OSAtomicOr32Barrier(set, (volatile uint32_t *)&info);
    }

    void clearInfo(uint32_t clear) {
        OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info);
    }


    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = this->info;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info));
    }

    bool hasCxxCtor() {
        // set_superclass propagates the flag from the superclass.
        return info & CLS_HAS_CXX_STRUCTORS;
    }

    bool hasCxxDtor() {
        return hasCxxCtor();  // one bit for both ctor and dtor
    }

    bool hasCustomRR() { 
        return true;
    }
    void setHasCustomRR(bool = false) { }
    void setHasDefaultRR() { }
    void printCustomRR(bool) { }

    bool hasCustomAWZ() { 
        return true;
    }
    void setHasCustomAWZ(bool = false) { }
    void setHasDefaultAWZ() { }
    void printCustomAWZ(bool) { }

    bool instancesHaveAssociatedObjects() {
        return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects() {
        setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }

    bool shouldGrowCache() {
        return info & CLS_GROW_CACHE;
    }

    void setShouldGrowCache(bool grow) {
        if (grow) setInfo(CLS_GROW_CACHE);
        else clearInfo(CLS_GROW_CACHE);
    }

    bool shouldFinalizeOnMainThread() {
        return info & CLS_FINALIZE_ON_MAIN_THREAD;
    }

    void setShouldFinalizeOnMainThread() {
        setInfo(CLS_FINALIZE_ON_MAIN_THREAD);
    }

    // +initialize bits are stored on the metaclass only
    bool isInitializing() {
        return getMeta()->info & CLS_INITIALIZING;
    }

    // +initialize bits are stored on the metaclass only
    void setInitializing() {
        getMeta()->setInfo(CLS_INITIALIZING);
    }

    // +initialize bits are stored on the metaclass only
    bool isInitialized() {
        return getMeta()->info & CLS_INITIALIZED;
    }

    // +initialize bits are stored on the metaclass only
    void setInitialized() {
        getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING);
    }

    bool isLoadable() {
        // A class registered for +load is ready for +load to be called
        // if it is connected.
        return isConnected();
    }

    IMP getLoadMethod();

    bool isFuture();

    bool isConnected();

    const char *mangledName() { return name; }
    const char *demangledName() { return name; }
    const char *nameForLogging() { return name; }

    bool isMetaClass() {
        return info & CLS_META;
    }

    // NOT identical to this->ISA() when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        return instance_size;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return (unalignedInstanceSize() + WORD_MASK) & ~WORD_MASK;
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

};
  • 3.3、isa
  • cache_t是用來存放每一個類中的方法緩存的數(shù)據(jù)結構,其本質是一個可增量擴展的哈希表數(shù)據(jù)結構,使用cache_t可以快速查找方法的執(zhí)行函數(shù),也是局部性原理的最佳應用。試想一下,如果一個類中有眾多的類方法與對象方法,有些方法可能在程序執(zhí)行的生命周期中只會調用一次,而在其他方法每一次調用時,系統(tǒng)都會遍歷類中所有的方法后找到其方法的相應IMP進行執(zhí)行,是一個非常浪費性能的工作,所以以此原因引入了cache_t,緩存方法列表,系統(tǒng)將一些常用方法儲存在類的緩存列表cache_t中,在每次方法調用時,先從緩存列表中查找,如果找到了對應方法,就直接調用其函數(shù)體,這是一種非常節(jié)約計算成本的方式。
  • 關于cache_t中的的數(shù)據(jù)結構,使用的一張哈希表存儲的眾多bucket_t數(shù)據(jù)結構,在bucket_t中包含的是方法的實現(xiàn)IMP與其對應的key,在一次消息傳遞的過程中,系統(tǒng)首先會在對應的類中進行緩存查找,利用發(fā)送消息的SEL查找cache_t哈希表,其過程是通過計算將SEL轉換成一個cache_key_t對象,利用該"key"進行cache_t哈希表定位,隨后取出bucket_t,直接拿到IMP指針,進行消息的發(fā)送。
  • class_data_bits_t數(shù)據(jù)結構。class_data_bits_t主要的作用是包含類中的方法、成員變量、協(xié)議等諸多主要信息,隨后再對一些零散信息的封裝。其實class_data_bits_t的主要作用也是對class_rw_t的一個封裝,重要的信息其實都在class_rw_t中
  • class_rw_t數(shù)據(jù)結構。class_rw_t代表了類相關的讀寫信息,以及對class_ro_t的封裝。顧名思義,rw就是讀寫,ro就是只讀。class_rw_t內的數(shù)據(jù)結構有,class_ro_t,protocols,properties,methods,第一個class_ro_t稍后詳細說明。后面三個其實就是對協(xié)議、屬性、方法的封裝,這三個數(shù)據(jù)結構都是以二維數(shù)組的形式提現(xiàn)的,但是為什么是二維數(shù)組呢?我之前寫過一篇關于分類理解的文章,里面說到分類方法添加的問題,就是分類在運行時決議的過程中,會把分類的方法都以數(shù)組的形式都添加到類方法中,其實過程就在這里,分類中眾多method_t以數(shù)組[method1,method2,method3]的形式提現(xiàn),但一個類可能有眾多分類,那么分類1,分類2,分類3在objc_class的class_data_bits_t的class_rw_t中的體現(xiàn)就是[[method1,method2,method3],[method1,method2,method3],[method1,method2,method3]],其都是method_t的數(shù)據(jù)形式。協(xié)議,屬性的體現(xiàn)方式都是相似的,就同理后推就好了。所以到這里肯定會有一個疑問,為什么class_rw_t中沒有成員變量只有屬性。因為class_rw_t中包含的是一個讀寫數(shù)據(jù)的列表,換言之其實就是分類的列表,class_rw_t只管分類中的數(shù)據(jù),之前分類那篇文章說到過,為分類添加屬性是不會生成相應的成員變量的,如果要生成對應的成員變量必須用關聯(lián)對象技術把使其達到一個類似于可讀寫效果,而其關聯(lián)對象都儲存在一個全局容器中,這也呼應了開頭介紹objc_object數(shù)據(jù)結構中存儲的相關關聯(lián)對象的方法。好了,越繞越遠了,現(xiàn)在繼續(xù)介紹class_ro_t數(shù)據(jù)結構
  • class_ro_t數(shù)據(jù)結構。這個數(shù)據(jù)結構包含的信息就是類本身的信息,包括name(類名),ivars(instence variables成員變量列表),properties(屬性列表),protocols(協(xié)議列表),methodList(方法列表)。所以這下好理解了吧,class_ro_t中裝的是類本身編譯的信息,class_rw_t中裝的是類中分類的信息,而class_data_bits_t封裝了class_rw_t封裝了class_ro_t。不過需要注意區(qū)別的是class_ro_t中包含的ivas,properties,protocols,methodList都是一位數(shù)組的形式存在(因為沒有分類了嘛)。所以這里還需要注意一個點就是:我們沒法向一個編譯后的類動態(tài)添加信息,比如方法,成員變量,屬性等,第一是因為這些都存在于class_ro_t中,其名稱含義就告訴你readOnly,人好好的存在那,說了不讓你改,你硬要改,那肯定不行啊,第二是因為所謂向一個類中動態(tài)添加信息,都是指的用runtime動態(tài)添加的類,通俗點講就是用代碼寫的類

  • 3.4、method_t

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

4、類對象與元類對象&消息傳遞相關面試問題

  • 類對象存儲實例方法列表等信息。

  • 元類對象存儲類方法列表等信息。

為什么會有元類呢?

  • 因為在Objective-C中,對象的方法并沒有存儲在對象的結構體中(如果每個對象都存儲自己的方法,那我們程序中無數(shù)對象就都要存儲自己的方法,那內存肯定就不夠用了)。當我們調用實例方法時,它通過自己的isa查找到對應的類,然后在class_data_bits_t結構體中查找對應的方法實現(xiàn)。每一個objc_class也有一個superClass指向自己的父類,可以查找到繼承的方法。那么如果調用實例方法怎么查找呢?這時,就需要引入元類來保證無論調用類方法和實例方法,都可以以相同的方式來查找。

消息傳遞的機制

  • 消息傳遞機制在runtime中的提現(xiàn)是一個方法objc_msgSend(object,SEL,types),當我們在OC中用[]調起一個方法的同時,runtime內部就會去調取這個方法進行消息方法,還有一個方法是objc_msgSendSuper,參數(shù)還是一樣,這個方法是我們用關鍵字super去調起方法時runtime內部所執(zhí)行的方法,其內部包含一個變量receiver,這個變量指向的就是super的子類self自己。之前看過一個案例,是在init內部調用[self class]和[super class],然后同時打印這兩個類名。其實結果不出意外是一樣的,但分析原因的話就是因為[super class]調起時,調用的是objc_msgSendSuper這個方法,其中包含一個指針receiver,指向的就是super對象調用者self自己,也就是消息的接收者還是self,而class方法都是存在于較高父類(系統(tǒng)類中的),所以在方法遍歷的時候,[super class]和[self class]的區(qū)別就在于,super是從父類開始向上遍歷直至找到class方法,而self是從本類開始向上遍歷最后找到對應方法,而調用class最后的結果取決于消息的接收者,因為兩個方法的消息接收者都是self,所以不出意外,最后打印出的結果都是self類本身。

而實際消息當一個方法調用的消息發(fā)出后,消息傳遞的機制為:

  • 首先判斷方法為類方法還是實例方法 ->object找到父類class(若為類方法則再向上取到metaClass)->哈希查找objc_class的緩存列表cache_t試試能否用對用SEL直接找到函數(shù)體IMP->若找到,則直接進行函數(shù)體調用后結束消息放松,若找不到則遍歷對應的類或者元類的方法列表,如果方法列表是排序好的則使用二分法遍歷,如果沒有排序好則使用普通遍歷->如果找到了則進行函數(shù)體調用,結束消息傳遞流程,若找不到則繼續(xù)根據(jù)superClass指針找到父類,再重復之前的工作(遍歷緩存后遍歷本類中方法列表),在途中若找到了相應函數(shù)體實現(xiàn)IMP則進行調用,結束消息傳遞流程,如果至到NSObject中還沒有找到則進行消息轉發(fā)的流程
  • 緩存查找

    • 1、給定值是SEL,目標值是對應bucket_t中的IMP
    image
    • 2、當前類中查找

      • 1、對于已排序好的列表,采用二分查找算法查找方法對應執(zhí)行函數(shù)。

      • 2、對于沒有排序的列表,采用一般遍歷查找方法對應執(zhí)行函數(shù)。

    • 3、父類逐級查找


      image

5、消息轉發(fā)流程

關于消息轉發(fā)的流程,其實是系統(tǒng)給的一個消息再利用的過程,當消息傳遞流程中沒有方法來響應此消息時,開發(fā)者可通過重寫以下這四個方法來實現(xiàn)消息轉發(fā)的過程,以及去讓計算機做相應的工作。

  • 1、+ (BOOL)resolveInstanceMethod:(SEL)sel

若返回YES,則表明消息已處理,結束流程,若返回NO則進行下一個方法的調用

  • 2、- (id)forwardingTargetForSelector:(SEL)aSelector

此方法可以返回轉發(fā)消息的目標,若返回為nil則調用下一個方法

  • 3、- (NSMethodSignature *)methodSignatureForSelector

此方法可以返回方法的簽名,若返回nil則直接拋出異常,表明方法函數(shù)體指針無法被找到,若返回方法簽名,則調用下一個方法

  • 4、- (void)forwardInvocation:(NSInvocation *)anInvocation

此方法內部會決定是否已處理方法,如果到這個方法也沒法處理,則拋出異常,報錯

以上四個方法是系統(tǒng)留給開發(fā)者處理消息轉發(fā)的入口,開發(fā)者可以通過重寫以上四個方法來手動處理消息轉發(fā)的流程。

image
image

6、Method-Swizzling

image
image

7、動態(tài)添加方法

image

8、動態(tài)方法解析

  • @dynamic

    • 動態(tài)運行時語言將函數(shù)決議推遲到運行時。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容