<簡書 — 劉小壯> https://www.jianshu.com/p/3019605a4fc9html
本文基於objc-723
版本,在Apple Github和Apple 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
語句,設置isa
的cls
爲傳入的Class
。isa
是objc_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; }
在對象銷燬時,運行時環境會調用NSObject
的dealloc
方法執行銷燬代碼,並不須要咱們手動去調用。接着會調用到Runtime
內部的objc_object::rootDealloc
(C++命名空間)函數。
在rootDealloc
函數中會執行一些釋放前的操做,例如將對象全部的引用指向nil
,而且調用free
函數釋放內存空間等。
下面的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
函數實現的。在函數內部主要作了三件事:
.cxx_destruct
函數,在函數內部還會進行對應的release
操做。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
函數,完成最後的釋放操做。
在項目中常常會動態對方法列表進行操做,例如動態添加或替換一個方法,這時候會用到下面兩個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
函數調用過來的。若是replace
是NO
則直接返回IMP
,若是是YES
則替換方法原有實現。
若是添加的方法不存在,則建立一個method_list_t
結構體指針,並設置三個基本參數name
、types
、imp
,而後經過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])); // ... }
在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
函數內部都是初始化操做,用來初始化剛剛建立的Class
和metaClass
。
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
中,全部類都是存在一個哈希表中的,在table
的buckets
中存儲。每次新建立類以後,都須要把該類加入到哈希表中,下面是向哈希表插入的邏輯。
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
的結構,這樣在訪問改變以前建立的對象時,就會出現問題。
以上圖爲例,在項目中建立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
函數重載後的結果,重載結果纔是運行時真正執行的函數。函數重載發生在編譯期,會包含namespace
、class name
、function name
、返回值、參數等部分,根據這些部分從新生成函數名。
在OC中其實也存在函數重載的概念,只不過OC並非直接對原有方法名作修改,而是增長對返回值和參數按照必定規則進行編碼,而後放在method_t
結構體中。
method_t
結構體存儲着方法的信息,其中types
字段就是返回值和參數的編碼。編碼後的字符串相似於"iv@:d"
,完整的編碼規則能夠查看官方文檔。
下面就是Method
的定義,主要包含了三個關鍵信息。
struct method_t { SEL name; const char *types; IMP imp; };
咱們在項目中常用協議,那協議又是怎麼實現的呢?
根據Runtime
源碼能夠看出,協議都是protocol_t
結構體的對象,而protocol_t
結構體是繼承自objc_object
的,因此具有對象的特徵。
除了objc_object
中定義的一些結構體參數外,protocol_t
中還定義了一些獨有的參數,例如經常使用的name
、method list
、property list
、size
等。因此能夠看出,一個協議中能夠聲明對象方法、類方法,以及對象屬性和類屬性。
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
是由objc_selector
結構體實現的,可是從如今的源碼來看,SEL
是一個const char*
的常量字符串,只是表明一個名字而已。
typedef struct objc_selector *SEL;
爲何說SEL
只是一個常量字符串呢?咱們在Runtime
源碼中探究一下。
這是在_read_images
函數中SEL list
的實現,主要邏輯是加載SEL list
到內存中,而後經過sel_registerNameNoLock
函數,將全部SEL
都註冊到屬於SEL
的哈希表中。
可是咱們從這段代碼中能夠看出,大部分的SEL
和const 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麻煩各位大佬點個贊,謝謝!😁