源码objc-private.h
中
struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(bool authenticated = false); ··· }
isa_t
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } uintptr_t bits; private: // Accessing the class requires custom ptrauth operations, so // force clients to go through setClass/getClass by making this // private. Class cls; public: #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; };
ISA_BITFIELD
# if __arm64__ // ARM64 simulators have a larger address space, so use the ARM64e // scheme even when simulators build for ARM64-not-e. # if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR # define ISA_MASK 0x007ffffffffffff8ULL # define ISA_MAGIC_MASK 0x0000000000000001ULL # define ISA_MAGIC_VALUE 0x0000000000000001ULL # define ISA_HAS_CXX_DTOR_BIT 0 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t weakly_referenced : 1; \ uintptr_t shiftcls_and_sig : 52; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7)
上面联合体 isa_t
涉及到一个位域的概念,可以参考《C语言位域(位段)详解》。
因为部分数据在存储时候并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
isa_t
使用了位域,这里 ISA_BITFIELD
位域成员通过跟 bits
相与来取对应的值。 在 ISA_BITFIELD
中定义的参数的含义:
class_rw_t
objc-runtim-new.h
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA uint16_t index; #endif // explicit_atomic 是为了安全操作 explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; }
class_rw_ext_t 是class_rw_t的拓展
objc-runtime-new.h
struct class_rw_ext_t { DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t) class_ro_t_authed_ptr<const class_ro_t> ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; char *demangledName; uint32_t version; };
class-rw-t里的 methods、properties、protocols是二位数组
class method_array_t : public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> class property_array_t : public list_array_tt<property_t, property_list_t, RawPtr> class protocol_array_t : public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
class-ro-t
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif union { const uint8_t * ivarLayout; Class nonMetaclass; }; explicit_atomic<const char *> name; // With ptrauth, this is signed if it points to a small list, but // may be unsigned if it points to a big list. void *baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties;
class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的初始内容,我们拿 baseMethodList 举例子:
method_t
using MethodListIMP = IMP; struct method_t { static const uint32_t smallMethodListFlag = 0x80000000; method_t(const method_t &other) = delete; // The representation of a "big" method. This is the traditional // representation of three pointers storing the selector, types // and implementation. struct big { SEL name; //方法、函数名 const char *types; //编码(参数类型、返回值类型) MethodListIMP imp; //方法实现,指向方法、函数的指针 };
方法缓存
Class 内部结构中有个方法缓存(cache_t),调用了方法之后会缓存在里面,他用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。下面是整理出来的重要部分代码:
这里的散列表的原理就是,通过 @selector(methodName) & _mask 获得一个索引值,通过这个索引就能很快在 buckets 中拿到对应的 bucket_t(key, _imp);当然存放也是一样的方式。
存放:如果生成的索引在 buckets 下已经存在 data 。那么他会把 index - 1,减到零了还没有空闲位置,它会从数组最大值开始继续往前找位置,直到有位置;
获取:在拿到 bucket_t 后,会比较一下 key 与 @selector(methodName) 是否对应,如果不对应,那就回按照存放的那样方式一个一个找。如果存满了,buckets 就会走扩容。
这就是空间换时间。