<简书 — 刘小壮> https://www.jianshu.com/p/3019605a4fc9
博客配图
本文基于objc-723版本,在Apple Github和Apple OpenSource上有源码,但是需要自己编译。
重点来了~,可以到我的Github上下载编译好的源码,源码中已经写了大量的注释,方便读者研究。(如果觉得还不错,各位大佬麻烦点个Star?)
Runtime Analyze
对象的初始化流程
在对象初始化的时候,一般都会调用alloc+init方法实例化,或者通过new方法进行实例化。下面将会分析通过alloc+init的方式实例化的过程,以下代码都是关键代码。
前面两步很简单,都是直接进行函数调用。
+ (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 idcallAlloc(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代码块。
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字节。
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;
}dealloc
在对象销毁时,运行时环境会调用NSObject的dealloc方法执行销毁代码,并不需要我们手动去调用。接着会调用到Runtime内部的objc_object::rootDealloc(C++命名空间)函数。
在rootDealloc函数中会执行一些释放前的操作,例如将对象所有的引用指向nil,并且调用free函数释放内存空间等。
dealloc
下面的if-else语句中有判断条件,如果是ARC环境,并且当前对象定义了实例变量,才会进入else中执行object_dispose函数,否则进入上面的if语句。上面的if语句表示当前对象没有实例变量,则直接将当前对象free。
inline voidobjc_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函数,完成最后的释放操作。
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函数调用过来的。如果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])); // ...}添加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函数内部都是初始化操作,用来初始化刚刚创建的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的结构,这样在访问改变之前创建的对象时,就会出现问题。
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;
};Protocol
我们在项目中经常使用协议,那协议又是怎么实现的呢?
根据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 voidreadProtocol(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的哈希表中。
但是我们从这段代码中可以看出,大部分的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
下载地址:Runtime PDF
麻烦各位大佬点个赞,谢谢!?
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章





