我们都知道,Runtime 是 Objective-C 这门动态语言的核心,只有理解了它,我们才能够更好的理解 Objective-C 到底是如何工作的,在编程时,也会更加得心应手。由于时间和精力有限,此次我主要想从以下几方面来进行 Runtime 源码的阅读,日后将会逐步完善。由于总体篇幅较长,所以我将会每一部分拆分成一篇文章来具体分析。
一、对象的本质,了解 isa
二、对象的生命周期
三、对象的引用计数
四、对象的扩展方法
五、Runtime 的应用
/// Represents an instance of a class. struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); }; // 其内部有一个指向 Class 的指针,而 Class 是什么呢 // An opaque type that represents an Objective-C class. typedef struct objc_class *Class; // 再来看一下 objc_class 的定义,需要注意的是,objc_class 也是继承自 objc_object, 由于 objc_object 中已经定义了一个 isa 指针,由于结构体中所有的成员都是 public 的,所以 objc_class 也就拥有了 isa 并且也拥有访问 isa 的权限。 struct objc_class : objc_object { // Class ISA; // 此时,类中的 isa 指针指向的是 metaClass 元类 Class superclass; // 父类 cache_t cache; // formerly cache pointer and vtable 类的方法缓存,因为 Runtime 时会把第一次遇到的方法缓存到方法缓存中,此后将直接从缓存中读取方法,极大提高了效率 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags // class_data_bits_t class_rw_t + 一个 rr/alloc 位 };
所以到这里,我们也就理解到了,实际上,类也是一个对象
我们需要知道的是,在 Objective-C 中,对象的方法并没有存储于对象的结构体中(如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响)。在调用实例方法时,而是通过 isa 指针来寻找相应的类,通过 class_data_bits_t 来寻找类中的方法。具体是如何寻找的,我们看
// 首先 class_data_bits_t 中有一个 bits 位 struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; public: // 这里返回的数据是 class_rw_t* 指针类型的数据,在这个方法中我们可以看出,将 bits 与 FAST_DATA_MASK 进行位运算,只取其中的 [3, 47] 位转换成 class_rw_t * 返回。 class_rw_t* data() { return (class_rw_t *)(bits & FAST_DATA_MASK); } } // data() 返回的是一个 class_rw_t* 指针, class_rw_t 又是什么? // 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中: struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; #if SUPPORT_INDEXED_ISA uint32_t index; #endif }; // 由此我们可以看出, class_rw_t 中包含了一些关于类的信息,比如 flag, 版本号, 方法数组, 属性数组等。而其中又有一个指向 class_ro_t 的指针, class_ro_t 又是什么? // 原来,class_ro_t 中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。 struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } };
所以由此,我们知道 class_ro_t 保存的是在编译期时就已经确定的方法,所以当在编译期时, class_data_bits_t 将直接指向 class_ro_t ,而后在 Runtime 时,将会调用 class_data_bits_t 的 data() 直接将结果从 class_rw_t 转化成 class_ro_t 指针, 然后再初始化一个 class_rw_t 指针,此时它中的数据都为空,然后再设置它的 ro 变量和 flag, 最后再为其设置正确的 data
/*********************************************************************** * realizeClass * Performs first-time initialization on class cls, * including allocating its read-write data. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static Class realizeClass(Class cls) { runtimeLock.assertWriting(); const class_ro_t *ro; class_rw_t *rw; Class supercls; Class metacls; bool isMeta; if (!cls) return nil; if (cls->isRealized()) return cls; assert(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? // 强制转化为 ro ro = (const class_ro_t *)cls->data(); if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // Normal class. Allocate writeable class data. // 这时候首先给 rw 分配内存空间并且初始化为 0 rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // 使 rw 指向 ro rw->ro = ro; // 设置 rw 的 flag 为 正在初始化和已经初始化 /** // class is realized - must never be set by compiler #define RO_REALIZED (1data is class_rw_t, not class_ro_t #define RW_REALIZED (1setData(rw); } // 判断是否为 metaClass RO_META (1 << 0) isMeta = ro->flags & RO_META; rw->version = isMeta ? 7 : 0; // old runtime went up to 6 // Choose an index for this class. // Sets cls->instancesRequireRawIsa if indexes no more indexes are available cls->chooseClassArrayIndex(); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex()); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA())); #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; if (DisableNonpointerIsa) { // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true; } else if (!hackedDispatch && !(ro->flags & RO_META) && 0 == strcmp(ro->name, "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->superclass && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsa(rawIsaIsInherited); } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping cls->superclass = supercls; cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable. if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSize if it wasn't set already. cls->setInstanceSize(ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) { cls->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); } } // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); } else { addRootClass(cls); } // Attach categories // 在这个方法中将 rw 的方法列表,属性列表,协议列表赋值 methodizeClass(cls); return cls; };
此时,经过 Runtime 的作用之后,现在内存中的关系是,类中的 data 指针指向 class_data_bits_t, class_data_bits_t 结构体中的 data() 方法获取到的是 class_rw_t 指针, class_rw_t 结构体中的 ro 指针指向 class_ro_t。图如下:
但是问题来了,类的方法是如何被查找和调用的呢?由于我们已经知道了,在 ObjC 中,实际上类也是一个特殊的对象,查找类的方法实际上就和查找实例方法采用同样的机制,但是如何才能让他们采用同样的机制呢?这时,元类的作用就显现了出来。
metaClass 保证了类中也有一个指向 Class 类型的指针,保证了类和对象的一致性,保证了类查找方法的机制与对象查找方法的机制保持同步。
当实例方法调用时,通过对象的isa在类中获取方法的实现
当类方法调用时,通过类的isa在元类中获取方法的实现
我们在 Runtime 的源码中可以看到,在不同的处理器上,这个共同体所分配的内存位数是不同的。
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if SUPPORT_PACKED_ISA // extra_rc must be the MSB-most field (so it matches carry/overflow flags) // nonpointer must be the LSB (fixme or get rid of it) // shiftcls must occupy the same bits that a real class pointer would // bits + RC_ONE is equivalent to extra_rc + 1 // RC_HALF is the high bit of extra_rc (i.e. half of its range) // future expansion: // uintptr_t fast_rr : 1; // no r/r overrides // uintptr_t lock : 2; // lock for atomic property, @synch // uintptr_t extraBytes : 1; // allocated with extra bytes # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 19; # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 8; # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) }; };
以 x86_64_ 为例,
更深一步,从 isa 的初始化来看 isa 的结构
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { if (!indexed) { isa.cls = cls; } else { isa.bits = ISA_MAGIC_VALUE; isa.has_cxx_dtor = hasCxxDtor; isa.shiftcls = (uintptr_t)cls >> 3; } } // 由于对象的 isa 初始化时传入 indexed 为 true ,所以,可简化为 inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { // 其中 ISA_MAGIC_VALUE 为 0x000001a000000001ULL isa.bits = ISA_MAGIC_VALUE; isa.has_cxx_dtor = hasCxxDtor; isa.shiftcls = (uintptr_t)cls >> 3; }
此时,当执行完 isa.bits = ISA_MAGIC_VALUE; 后 isa 的结构为 ,可以看到 ISA_MAGIC_VALUE 将 magic 和 indexed 都初始化了
接着 isa.has_cxx_dtor = hasCxxDtor; 这一位会设置 has_cxx_dtor 的值,如果是 1, 则表示当前对象是否有析构器,如果没有,就会快速释放
最后, isa.shiftcls = (uintptr_t)cls >> 3; 将当前对象对应的类指针赋值给 shiftcls 这些位,之所以向右移三位,移三位的主要原因是用于将 Class 指针中无用的后三位清楚减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。赋值之后如下
![image](http://upload-images.jianshu.io/upload_images/3262069-f3a02c34c16975e6?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 至此,也就证明了我们之前对于初始化 `isa` 时对 `initIsa` 方法的分析是正确的。它设置了 `indexed`、`magic` 以及 `shiftcls`。
由于我们现在使用了结构体 isa_t 来替代 Class 类型的指针, 所以我们也就需要一个指针能够返回 isa 所指的类,所以我们此时需要一个 ISA() 方法。
inline Class objc_object::ISA() { assert(!isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); #endif } // 简化后如下 inline Class objc_object::ISA() { assert(!isTaggedPointer()); // 由此可以看到,实际上 ISA() 返回的是 isa.bits 与 0x0000000ffffffff8ULL 进行的按位与操作,确实可以返回当前的类 return (Class)(isa.bits & ISA_MASK); }
至此,此次源码分析的第一部分也就此结束,如果您发现了什么问题和不足欢迎与我探讨和指教。
作者:shenglanya
链接:https://www.jianshu.com/p/2879f56e7df6