探祕Runtime - Runtime源碼分析

該文章屬於<簡書 — 劉小壯>原創,轉載請註明:

<簡書 — 劉小壯> https://www.jianshu.com/p/3019605a4fc9html


博客配圖


本文基於objc-723版本,在Apple GithubApple OpenSource上有源碼,可是須要本身編譯。git

重點來了~,能夠到個人Github上下載編譯好的源碼,源碼中已經寫了大量的註釋,方便讀者研究。(若是以爲還不錯,各位大佬麻煩點個Star😁) Runtime Analyzegithub

對象的初始化流程

在對象初始化的時候,通常都會調用alloc+init方法實例化,或者經過new方法進行實例化。下面將會分析經過alloc+init的方式實例化的過程,如下代碼都是關鍵代碼。數組

前面兩步很簡單,都是直接進行函數調用。app

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
複製代碼

在建立對象的地方有兩種方式,一種是經過calloc開闢內存,而後經過initInstanceIsa函數初始化這塊內存。第二種是直接調用class_createInstance函數,由內部實現初始化邏輯。框架

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (fastpath(cls->canAllocFast())) {
        bool dtor = cls->hasCxxDtor();
        id obj = (id)calloc(1, cls->bits.fastInstanceSize());
        if (slowpath(!obj)) return callBadAllocHandler(cls);
        obj->initInstanceIsa(cls, dtor);
        return obj;
    }
    else {
        id obj = class_createInstance(cls, 0);
        if (slowpath(!obj)) return callBadAllocHandler(cls);
        return obj;
    }
}
複製代碼

可是在最新版的objc-723中,調用canAllocFast函數直接返回false,因此只會執行上面第二個else代碼塊。ide

bool canAllocFast() {
    return false;
}
複製代碼

初始化代碼最終會調用到_class_createInstanceFromZone函數,這個函數是初始化的關鍵代碼。下面代碼中會進入if語句內,根據instanceSize函數返回的size,經過calloc函數分配內存,並初始化isa_t指針。函數

id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline))
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                                 bool cxxConstruct = true, 
                                 size_t *outAllocatedSize = nil)
{
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size = cls->instanceSize(extraBytes);

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;
        obj->initIsa(cls);
    }

    return obj;
}
複製代碼

instanceSize()函數中,會經過alignedInstanceSize函數獲取對象原始大小,在class_ro_t結構體中的instanceSize變量中定義。這個變量中存儲了對象實例化時,全部變量所佔的內存大小,這個大小是在編譯器就已經決定的,不能在運行時進行動態改變。佈局

獲取到instanceSize後,對獲取到的size進行地址對其。須要注意的是,CF框架要求全部對象大小最少是16字節,若是不夠則直接定義爲16字節。ui

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;
}
複製代碼

這也是很關鍵的一步,因爲調用initIsa函數時,nonpointer字段傳入true,因此直接執行if語句,設置isacls爲傳入的Classisaobjc_object的結構體成員變量,也就是isa_t的類型。

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
        isa = newisa;
    }
}
複製代碼

經過new函數建立對象實際上是同樣的,內部經過callAlloc函數執行建立操做,若是調用alloc方法的話也是調用的callAlloc函數。因此調用new函數初始化對象時,能夠等同於alloc+init的調用。

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
複製代碼

在runtime源碼中,執行init操做本質上就是直接把self返回。

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}
複製代碼

dealloc

在對象銷燬時,運行時環境會調用NSObjectdealloc方法執行銷燬代碼,並不須要咱們手動去調用。接着會調用到Runtime內部的objc_object::rootDealloc(C++命名空間)函數。

rootDealloc函數中會執行一些釋放前的操做,例如將對象全部的引用指向nil,而且調用free函數釋放內存空間等。

dealloc

下面的if-else語句中有判斷條件,若是是ARC環境,而且當前對象定義了實例變量,纔會進入else中執行object_dispose函數,不然進入上面的if語句。上面的if語句表示當前對象沒有實例變量,則直接將當前對象free

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
複製代碼

object_dispose函數中,主要是經過objc_destructInstance函數實現的。在函數內部主要作了三件事:

  1. 對當前對象進行析構,會調用析構函數.cxx_destruct函數,在函數內部還會進行對應的release操做。
  2. 移除當前對象的全部關聯關係。
  3. 進行最後的clear操做。
// dealloc方法的核心實現,內部會作判斷和析構操做
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // 判斷是否有OC或C++的析構函數
        bool cxx = obj->hasCxxDtor();
        // 對象是否有相關聯的引用
        bool assoc = obj->hasAssociatedObjects();

        // 對當前對象進行析構
        if (cxx) object_cxxDestruct(obj);
        // 移除全部對象的關聯,例如把weak指針置nil
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
複製代碼

上面的函數中會調用object_cxxDestruct函數進行析構,而函數內部是經過object_cxxDestructFromClass函數實現的。

函數內部會從當前對象所屬的類開始遍歷,一直遍歷到根類位置。在遍歷的過程當中,會不斷執行.cxx_destruct函數,對傳入的對象進行析構。

由於在繼承者鏈中,每一個類都會有本身的析構代碼,因此須要將當前對象傳入,並逐個執行析構操做,將對象的全部析構操做都執行完成才能夠。

// 調用C++的析構函數
static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // 從當前類開始遍歷,直到遍歷到根類
    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return;
        // SEL_cxx_destruct就是.cxx_destruct的selector
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            // 獲取到.cxx_destruct的函數指針並調用
            (*dtor)(obj);
        }
    }
}
複製代碼

在對象被執行.cxx_destruct析構函數後,析構函數內部還會調用一次release函數,完成最後的釋放操做。

cxx_destruct

addMethod實現

在項目中常常會動態對方法列表進行操做,例如動態添加或替換一個方法,這時候會用到下面兩個Runtime函數。在下面兩個函數中,本質上都是經過addMethod函數實現的,在class_addMethod中對返回值進行了一個取反,因此若是此函數返回NO則表示方法已存在,不要重複添加。

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    rwlock_writer_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    rwlock_writer_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}
複製代碼

下面咱們就分析一下addMethod函數的實現,依然只保留核心源碼。

addMethod函數中會先判斷須要添加的方法是否存在,若是已經存在則直接返回對應的IMP,不然就動態添加一個方法。在class_addMethod函數中有一個replace字段,表示區別是否class_replaceMethod函數調用過來的。若是replaceNO則直接返回IMP,若是是YES則替換方法原有實現。

若是添加的方法不存在,則建立一個method_list_t結構體指針,並設置三個基本參數nametypesimp,而後經過attachLists函數將新建立的method_list_t結構體添加到方法列表中。

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}
複製代碼

attachLists函數中實現比較簡單,經過對原有地址作位移,並將新建立的method_list_t結構體copy到方法列表中。

void attachLists(List* const * addedLists, uint32_t addedCount) {
    // ...
    memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
    memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
    // ...
}
複製代碼

添加Ivar

Runtime中能夠經過class_addIvar函數,向一個類添加實例對象。可是須要注意的是,這個函數不能向一個已經存在的類添加實例變量,只能想經過Runtime API建立的類動態添加實例變量。

函數應該在調用objc_allocateClassPair函數建立類以後,以及調用objc_registerClassPair函數註冊的類之間添加實例變量,不然就會失敗。也不能向一個元類添加實例變量,只能想類添加實例變量。

下面是動態建立一個類,並向新建立的類添加實例變量的代碼。

Class testClass = objc_allocateClassPair([NSObject class], "TestObject", 0);
BOOL isAdded = class_addIvar(testClass, "password", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
objc_registerClassPair(testClass);

if (isAdded) {
    id object = [[testClass alloc] init];
    [object setValue:@"lxz" forKey:@"password"];
}
複製代碼

那麼,爲何須要把動態添加實例變量的代碼放在這兩個函數中間呢?讓咱們一塊兒來探究一下吧。

首先經過objc_allocateClassPair函數來建立類,建立時經過getClass函數判斷類名是否已用,而後經過verifySuperclass函數判斷superclass是否合適,若是任意條件不符合則建立類失敗。

下面經過alloc_class_for_subclass函數建立類和元類,在alloc函數內部本質上是經過calloc函數分配內存空間,沒有作其餘操做。而後就執行objc_initializeClassPair_internal函數,initialize函數內部都是初始化操做,用來初始化剛剛建立的ClassmetaClass

Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;
    if (getClass(name)  ||  !verifySuperclass(superclass, true/*rootOK*/)) {
        return nil;
    }

    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);

    objc_initializeClassPair_internal(superclass, name, cls, meta);
    return cls;
}
複製代碼

這就是initialize函數內部的實現,都是各類初始化代碼,沒有作其餘邏輯操做。至此,類的初始化完成,能夠在外面經過class_addIvar函數添加實例變量了。

static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
    class_ro_t *cls_ro_w, *meta_ro_w;
    
    cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    cls_ro_w   = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    meta_ro_w  = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    cls->data()->ro = cls_ro_w;
    meta->data()->ro = meta_ro_w;

    // Set basic info
    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    cls->data()->version = 0;
    meta->data()->version = 7;

    // ....
}
複製代碼

在建立類以後,會經過objc_registerClassPair函數註冊新類。和建立新類同樣,註冊新類也分爲註冊類和註冊元類。經過下面的addNonMetaClass函數註冊元類,經過直接調用NXMapInsert函數註冊類。

void objc_registerClassPair(Class cls)
{
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

    addNamedClass(cls, cls->data()->ro->name);
}

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    Class old;
    if ((old = getClass(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
}
複製代碼

不管是註冊類仍是註冊元類,內部都是經過NXMapInsert函數實現的。在Runtime中,全部類都是存在一個哈希表中的,在tablebuckets中存儲。每次新建立類以後,都須要把該類加入到哈希表中,下面是向哈希表插入的邏輯。

void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    // 計算key在當前hash表中的下標,hash下標不必定是最後
    unsigned	index = bucketOf(table, key);
    // 找到buckets的首地址,並經過index下標計算對應位置,獲取到index對應的MapPair
    MapPair	*pair = pairs + index;
    // 若是key爲空,則返回
    if (key == NX_MAPNOTAKEY) {
        _objc_inform("*** NXMapInsert: invalid key: -1\n");
        return NULL;
    }

    unsigned numBuckets = table->nbBucketsMinusOne + 1;
    // 若是當前地址未衝突,則直接對pair賦值
    if (pair->key == NX_MAPNOTAKEY) {
        pair->key = key; pair->value = value;
        table->count++;
        if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
            return NULL;
    }
    
    /* 到這一步,則表示hash表衝突了 */
    
    // 若是同名,則將舊類換爲新類
    if (isEqual(table, pair->key, key)) {
        const void	*old = pair->value;
        if (old != value) pair->value = value;
            return (void *)old;
    
    // hash表滿了,對hash表作重哈希,而後再次執行這個函數
    } else if (table->count == numBuckets) {
        /* no room: rehash and retry */
        _NXMapRehash(table);
        return NXMapInsert(table, key, value);

    // hash表衝突了
    } else {
        unsigned	index2 = index;
        // 解決hash表衝突,這裏採用的是線性探測法,解決哈希表衝突
        while ((index2 = nextIndex(table, index2)) != index) {
            pair = pairs + index2;
            if (pair->key == NX_MAPNOTAKEY) {
                pair->key = key; pair->value = value;
                table->count++;
                // 在查找過程當中,發現哈希表不夠用了,則進行重哈希
                if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
                    return NULL;
            }
            // 找到同名類,則用新類替換舊類,並返回
            if (isEqual(table, pair->key, key)) {
                const void	*old = pair->value;
                if (old != value) pair->value = value;
                    return (void *)old;
            }
        }
    return NULL;
    }
}
複製代碼

思考

那爲何只能向運行時動態建立的類添加ivars,不能向已經存在的類添加ivars呢?

這是由於在編譯時只讀結構體class_ro_t就會被肯定,在運行時是不可更改的。ro結構體中有一個字段是instanceSize,表示當前類在建立對象時須要多少空間,後面的建立都根據這個size分配類的內存。

若是對一個已經存在的類增長一個參數,改變了ivars的結構,這樣在訪問改變以前建立的對象時,就會出現問題。

ivars

以上圖爲例,在項目中建立TestObject類,而且添加三個成員變量,其ivars的內存結構佔用20字節。若是在運行時動態添加一個bool型參數,以後建立的對象ivars都佔用21字節。

在經過ivars結構體訪問以前建立的對象時,由於以前建立的對象沒有sex,因此仍是按照20字節分配的內存空間,這時候訪問sex就會致使地址越界。

數據訪問

定義對象時都會給其設置類型,類型本質上並非一個對象,而是用來標示當前對象所佔空間的。以C語言爲例,訪問對象都是經過地址作訪問的,而類型就是從首地址開始讀取多少位是當前對象。

int number = 18;
char text = 'i';
複製代碼

以上面代碼爲例,定義了一個int類型的number,佔用四字節,定義一個char類型的text變量,佔用一字節。在內存中訪問對象時,就是根據指針地址找到對應的內存區,而後按照指針類型取多少範圍的內存,就完成對象的讀取操做。

內存佈局

而在面嚮對象語言中,函數或方法的命名規則還須要保留在運行期。以C++爲例,C++中有一個概念叫作「函數重載」,函數重載指的是容許有一組相同函數名,但參數列表類型不一樣的函數。

原函數:void print(char c)
重載結果:_ZN4test5printEc
複製代碼

C++函數重載是有必定規則的,例如上面就是對print函數重載後的結果,重載結果纔是運行時真正執行的函數。函數重載發生在編譯期,會包含namespaceclass namefunction name、返回值、參數等部分,根據這些部分從新生成函數名。

在OC中其實也存在函數重載的概念,只不過OC並非直接對原有方法名作修改,而是增長對返回值和參數按照必定規則進行編碼,而後放在method_t結構體中。

method_t結構體存儲着方法的信息,其中types字段就是返回值和參數的編碼。編碼後的字符串相似於"iv@:d",完整的編碼規則能夠查看官方文檔

下面就是Method的定義,主要包含了三個關鍵信息。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};
複製代碼

Protocol

咱們在項目中常用協議,那協議又是怎麼實現的呢?

根據Runtime源碼能夠看出,協議都是protocol_t結構體的對象,而protocol_t結構體是繼承自objc_object的,因此具有對象的特徵。

除了objc_object中定義的一些結構體參數外,protocol_t中還定義了一些獨有的參數,例如經常使用的namemethod listproperty listsize等。因此能夠看出,一個協議中能夠聲明對象方法、類方法,以及對象屬性和類屬性。

struct protocol_t : objc_object {
    const char *mangledName;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    // Fields below this point are not always present on disk.
    const char **_extendedMethodTypes;
    const char *_demangledName;
    property_list_t *_classProperties;
};
複製代碼

既然具有了對象的特徵,那也是有isa指針的。在Protocol中全部的isa都指向同一個類Protocol。Protocol類中沒有作太複雜的處理,只是實現了一些基礎的方法。

@implementation Protocol 

+ (void) load {

}

- (BOOL) conformsTo: (Protocol *)aProtocolObj {
    return protocol_conformsToProtocol(self, aProtocolObj);
}

- (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel {
    return method_getDescription(protocol_getMethod((struct protocol_t *)self, 
                                                     aSel, YES, YES, YES));
}

- (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel {
    return method_getDescription(protocol_getMethod((struct protocol_t *)self, 
                                                    aSel, YES, NO, YES));
}

- (const char *)name {
    return protocol_getName(self);
}

// Protocol重寫了isEqual方法,內部不斷查找其父類,判斷是否Protocol的子類。
- (BOOL)isEqual:other {
    Class cls;
    Class protoClass = objc_getClass("Protocol");
    for (cls = object_getClass(other); cls; cls = cls->superclass) {
        if (cls == protoClass) break;
    }
    if (!cls) return NO;
    // check equality
    return protocol_isEqual(self, other);
}

- (NSUInteger)hash {
    return 23;
}

@end
複製代碼

協議的初始化也是在_read_images函數中完成的,初始化過程主要是一個遍歷。邏輯就是獲取Protocol list,而後遍歷這個數組,並調用readProtocol函數進行初始化操做。

// 遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    // cls = Protocol類,全部協議和對象的結構體都相似,isa都對應Protocol類
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    assert(cls);
    // 獲取protocol哈希表
    NXMapTable *protocol_map = protocols();
    bool isPreoptimized = hi->isPreoptimized();
    bool isBundle = hi->isBundle();

    // 從編譯器中讀取並初始化Protocol
    protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}
複製代碼

readProtocol函數中,會根據傳入的協議進行初始化操做。在傳入參數中,protocol_class就是Protocol類,全部的協議類的isa都指向這個類。

根據Protocol的源碼能夠看出,其對象模型是比較簡單的,和Class的對象模型還不太同樣。Protocol的對象模型只有從Protocol list中加載的對象和isa指向的Protocol類構成,沒有其餘的實例化過程,Protocol類並無元類。

// 初始化傳入的全部Protocol,若是哈希表中已經存在初始化的Protocol,則不作任何處理
static void
readProtocol(protocol_t *newproto, Class protocol_class,
             NXMapTable *protocol_map, 
             bool headerIsPreoptimized, bool headerIsBundle)
{
    auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;
    // 根據名字得到對應的Protocol對象
    protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);

    // 若是Protocol不爲NULL,表示已經存在相同的Protocol,則不作任何處理,進入下面if語句。
    if (oldproto) {
        // nothing
    }
    // 若是Protocol爲NULL,則對其進行簡單的初始化,並將Protocol的isa設置爲Protocol類
    else if (headerIsPreoptimized) {
        protocol_t *cacheproto = (protocol_t *)
            getPreoptimizedProtocol(newproto->mangledName);
        protocol_t *installedproto;
        if (cacheproto  &&  cacheproto != newproto) {
            installedproto = cacheproto;
        }
        else {
            installedproto = newproto;
        }
        // 哈希表插入函數的指針
        insertFn(protocol_map, installedproto->mangledName, 
                 installedproto);
    }
    // 下面兩個else都是初始化protocol_t的過程
    else if (newproto->size >= sizeof(protocol_t)) {
        newproto->initIsa(protocol_class);
        insertFn(protocol_map, newproto->mangledName, newproto);
    }
    else {
        size_t size = max(sizeof(protocol_t), (size_t)newproto->size);
        protocol_t *installedproto = (protocol_t *)calloc(size, 1);
        memcpy(installedproto, newproto, newproto->size);
        installedproto->size = (__typeof__(installedproto->size))size;
        
        installedproto->initIsa(protocol_class);
        insertFn(protocol_map, installedproto->mangledName, installedproto);
    }
}
複製代碼

Protocol是能夠在運行時動態建立添加的,和建立Class的過程相似,分爲建立和註冊兩部分。 建立Protocol以後,Protocol處於一個未完成的狀態,只有註冊後纔是可使用的Protocol

// 建立新的Protocol,建立後還須要調用下面的register方法
Protocol *
objc_allocateProtocol(const char *name)
{
    if (getProtocol(name)) {
        return nil;
    }

    protocol_t *result = (protocol_t *)calloc(sizeof(protocol_t), 1);

    // 下面的cls是__IncompleteProtocol類,表示是未完成的Protocol
    extern objc_class OBJC_CLASS_$___IncompleteProtocol;
    Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol;
    result->initProtocolIsa(cls);
    result->size = sizeof(protocol_t);
    result->mangledName = strdupIfMutable(name);
    
    return (Protocol *)result;
}
複製代碼

註冊Protocol

// 向protocol的哈希表中,註冊新建立的Protocol對象
void objc_registerProtocol(Protocol *proto_gen) 
{
    protocol_t *proto = newprotocol(proto_gen);

    extern objc_class OBJC_CLASS_$___IncompleteProtocol;
    Class oldcls = (Class)&OBJC_CLASS_$___IncompleteProtocol;
    extern objc_class OBJC_CLASS_$_Protocol;
    Class cls = (Class)&OBJC_CLASS_$_Protocol;

    // 若是已經被註冊到哈希表中,則直接返回
    if (proto->ISA() == cls) {
        return;
    }
    // 若是當前protocol的isa不是__IncompleteProtocol,表示這個protocol是有問題的,則返回
    if (proto->ISA() != oldcls) {
        return;
    }
    proto->changeIsa(cls);
    NXMapKeyCopyingInsert(protocols(), proto->mangledName, proto);
}
複製代碼

SEL

以前SEL是由objc_selector結構體實現的,可是從如今的源碼來看,SEL是一個const char*的常量字符串,只是表明一個名字而已。

typedef struct objc_selector *SEL;
複製代碼

爲何說SEL只是一個常量字符串呢?咱們在Runtime源碼中探究一下。

這是在_read_images函數中SEL list的實現,主要邏輯是加載SEL list到內存中,而後經過sel_registerNameNoLock函數,將全部SEL都註冊到屬於SEL的哈希表中。

可是咱們從這段代碼中能夠看出,大部分的SELconst char*的轉換,都是直接進行強制類型轉換的,因此兩者是同一塊內存。

// 將全部SEL都註冊到哈希表中,是另一張哈希表
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
    if (hi->isPreoptimized()) continue;

    bool isBundle = hi->isBundle();
    // 取出的是字符串數組,例如首地址是"class"
    SEL *sels = _getObjc2SelectorRefs(hi, &count);
    UnfixedSelectors += count;
    for (i = 0; i < count; i++) {
        // sel_cname函數內部就是將SEL強轉爲常量字符串
        const char *name = sel_cname(sels[i]);
        // 註冊SEL的操做
        sels[i] = sel_registerNameNoLock(name, isBundle);
    }
}
複製代碼

再進入sel_registerNameNoLock函數中能夠看出,SEL的哈希表也是將字符串註冊到哈希表中,並非以前的objc_selector結構體,因此能夠看出如今SEL就是單純的const char*常量字符串。

static SEL sel_alloc(const char *name, bool copy)
{
    return (SEL)(copy ? strdupIfMutable(name) : name);    
}
複製代碼

對等交換協議

研究Apple的源碼時,還能夠經過GNUStep研究,GNUStep是蘋果的一套對等交換源碼,將OC代碼以從新實現了一遍,內部實現大體和蘋果的相似。 GNUStep


簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github上,下載Runtime PDF合集。把全部Runtime文章總計九篇,都寫在這個PDF中,並且左側有目錄,方便閱讀。

Runtime PDF

下載地址:Runtime PDF 麻煩各位大佬點個贊,謝謝!😁

相關文章
相關標籤/搜索