<简书 — 刘小壮> 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麻烦各位大佬点个赞,谢谢!😁