Objective-C 之 Runtime 数据结构篇
2020/5/30 23:26:34
本文主要是介绍Objective-C 之 Runtime 数据结构篇,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
对象
当不确定一个对象的类型的时候,通常使用 id
类型进行表示
- (id)copy; 复制代码
id
代表一个对象,它是指向一个实例对象的指针
typedef struct objc_object *id; 复制代码
实际上,id
是一个 objc_object
结构体类型指针的别名
struct objc_object { isa_t isa; }; 复制代码
而 objc_object
这个结构体中只有一个 isa_t
类型的成员 isa
,它包含了当前对象所属于的类的信息。
isa_t
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif }; 复制代码
isa_t
是一个联合体,这就意味着 isa_t
中保存的既可以是一个 Class
类型的指针,也可以是一个 64
位的 bits
,但在某一个时刻,只能保存二者中的一个。
# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # define ISA_BITFIELD \ 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 # define ISA_BITFIELD \ 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) 复制代码
在不同架构下 bits
内存布局有所不同,但是结构体中的成员和其代表的含义都是相同的,只是具体结构体的实现和位数可能有些差别。
isa 初始化
inline void objc_object::initClassIsa(Class cls) { if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) { initIsa(cls, false/*not nonpointer*/, false); } else { initIsa(cls, true/*nonpointer*/, false); } } inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { initIsa(cls, true, hasCxxDtor); } 复制代码
不论是初始化类还是实例的 isa
都是调用了下面的方法
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { if (!nonpointer) { isa = isa_t((uintptr_t)cls); } else { isa_t newisa(0); newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; isa = newisa; } } 复制代码
如果是老版本 isa
中就是保存 Class
类型的指针。如果是新版本,就对 64
位的 bits
进行配置
其中 ISA_MAGIC_VALUE
实际上配置了结构体中成员 nonpointer
和 magic
的值。
nonpointer
如果是0
那么isa
中就是保存Class
类型的指针,如果是1
就保存的是bits
。magic
用于调试器判断当前对象是真的对象还是没有初始化的空间
接着配置了 has_cxx_dtor
的值,该值表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。
最后将类的指针右移了 3 位后赋值给了成员 shiftcls
。这是因为类的指针是按照字节(8bits)对齐的,其指针后三位都是没有意义的 0,因此可以右移 3 位进行消除,以减小无意义的内存占用。
isa_t 成员
成员名 | 含义 |
---|---|
nonpointer | 区分新老版本,老版本是 Class cls ,新版本是 uintptr_t bits |
has_assoc | 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存 |
has_cxx_dtor | 对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存 |
shiftcls | 类的指针 |
magic | 调试器判断当前对象是真的对象还是没有初始化的空间 |
weakly_referenced | 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放 |
deallocating | 对象正在释放内存 |
has_sidetable_rc | 对象的引用计数太大了,存不下 |
extra_rc | 对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,该值就为 9 |
类
在获取一个对象所属的类时,返回值的类型是 Class
- (Class)class; 复制代码
Class
代表一个类,它是指向一个类对象的指针
typedef struct objc_class *Class; 复制代码
实际上,Class
是一个 objc_class
结构体类型指针的别名
struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags }; 复制代码
可以看到 objc_class
中有三个成员。
-
首先,
objc_class
是继承自objc_object
,也就是说类也是一个对象。其中从objc_object
继承下来的成员isa
,它包含了当前类对象所属的元类的信息。 -
其次,成员
superclass
指向了该类的父类 -
接着,成员
cache
中缓存了最近调用过的方法 -
最后,成员
bits
根据注释可以知道bits
=指向 class_rw_t 类型结构体的指针
+自定义的 rr/alloc 标识
cache_t
struct cache_t { explicit_atomic<struct bucket_t *> _buckets; explicit_atomic<mask_t> _mask; uint16_t _occupied; }; 复制代码
用来缓存调用过方法的是一个 cache_t
类型的结构体,共有三个成员。
第一个成员 _buckets
是一个 bucket_t
类型的数组。
第二个成员 _mask
保存着总共申请用来缓存的数量。
第三个成员 _occupied
保存着目前已经使用缓存的数量。
bucket_t
struct bucket_t { #if __arm64__ explicit_atomic<uintptr_t> _imp; explicit_atomic<SEL> _sel; #else explicit_atomic<SEL> _sel; explicit_atomic<uintptr_t> _imp; #endif }; 复制代码
bucket_t
结构体中存储了键值对,其中 _imp
是一个函数指针,指向函数的具体实现,_sel
是方法名或者方法标识。也就说 _buckets
是一个哈希表,里面通过键值对的方式保存着调用过的方法。键为 SEL
类型,值为 IMP
类型。
SEL
typedef struct objc_selector *SEL; 复制代码
从声明上看 SEL
是一个 objc_selector
结构体指针的别名;从实现中找,发现 SEL
是指向一个 char *
变量,也就是说其实它就是个映射到方法的 C 字符串,可以理解为是区分方法的 ID。
SEL
只与方法名有关:
- 不同类中相同名字的方法所对应的方法选择器是相同的
- 方法名相同而变量类型不同所对应的方法选择器也是相同的
IMP
#if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif 复制代码
IMP
是一个函数指针,它指向函数的具体实现。
class_data_bits_t
bits
中保存着类的相关信息,它是一个 class_data_bits_t
类型的结构体,
struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; }; 复制代码
这个结构体中的成员和 isa_t
结构体相同,都是通过一个 64
位的 bits
储存信息
// class is a Swift class from the pre-stable Swift ABI #define FAST_IS_SWIFT_LEGACY (1UL<<0) // class is a Swift class from the stable Swift ABI #define FAST_IS_SWIFT_STABLE (1UL<<1) // class or superclass has default retain/release/autorelease/retainCount/ // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define FAST_HAS_DEFAULT_RR (1UL<<2) // data pointer #define FAST_DATA_MASK 0x00007ffffffffff8UL 复制代码
通过定义的宏可以知道这些位都代表了什么含义
FAST_IS_SWIFT_LEGACY
是否是来自预稳定 Swift ABI 的 Swift 类FAST_IS_SWIFT_STABLE
是否是来自已稳定 Swift ABI 的 Swift 类FAST_HAS_DEFAULT_RR
当前类或者父类含有默认的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法FAST_DATA_MASK
数据指针
搜索 FAST_DATA_MASK
的使用位置,可以找到一对取值/赋值方法
class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; } 复制代码
通过这两方法,可以知道数据指针是一个 class_rw_t
类型的指针
class_rw_t
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint16_t version; uint16_t witness; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; 复制代码
结构体 class_rw_t
名称中的 rw
代表 readwrite
,其中的成员 ro
是一个 class_ro_t
类型的指针,这里的 ro
代表 readonly
。ro
中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。而 class_rw_t
提供了运行时对类拓展的能力,其中的 methods
、properties
、protocols
保存着通过 Category 在运行时添加的方法、属性及协议。
成员 methods
、properties
、protocols
对应的 method_array_t
、property_array_t
、protocol_array_t
类型都是继承自 list_array_tt<Element, List>
,该类型可以保存三种类型的值:
- 空值;
- 指向单个列表的指针;
- 指向列表的指针数组。
通过第 3 种类型也就是二维数组类型,可以实现数据的扩展。例如,method_array_t
是一个数组,其中保存的元素是 method_list_t
,而 method_list_t
也是一个数组,其中保存的元素是 method_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; }; 复制代码
结构体中的成员 baseMethodList
、baseProperties
、baseProtocols
和 ivar_list_t
对应的 method_list_t
、property_list_t
、protocol_list_t
和 ivar_list_t
类型都是继承自 entsize_list_tt
,一个泛型的数组结构,没有扩展功能。
类的初始化
类在运行时第一次初始化时会调用 realizeClass...
系列方法
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 = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw); } 复制代码
在该方法的实现中,可以知道在编译期间类的结构中的 class_data_bits_t *data
指向的是一个 class_ro_t *
。在运行时的时候调用该初始化方法时,首先为 class_rw_t *
申请内存空间,然后将 class_ro_t *
赋值给 rw->ro
,接着设置 class_rw_t *
的标志位标识状态,最后将创建好的 class_rw_t *
赋值给类结构。
分类
Category 提供了在运行时动态的向已经存在的类中添加方法、协议和属性的功能
typedef struct category_t *Category; 复制代码
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; }; 复制代码
加载分类
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags) { constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; uint32_t mcount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); auto rw = cls->data(); for (uint32_t i = 0; i < cats_count; i++) { auto& entry = cats_list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { if (mcount == ATTACH_BUFSIZ) { prepareMethodLists(cls, mlists, mcount, NO, fromBundle); rw->methods.attachLists(mlists, mcount); mcount = 0; } mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } } if (mcount > 0) { prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) flushCaches(cls); } } 复制代码
原来的方法中包含了对分类中方法、代理和属性的处理,但原理相同,为了减少篇幅,只保留对方法的处理:
- 声明一个大小为 64,元素类型为
method_list_t *
的二维数组类型的临时变量mlists
- 遍历分类数组,获取分类中的方法列表
mlist
,从后向前的添加到mlists
中 - 如果
mlists
数组在遍历过程中存满,则合并到rw->methods
中,否则等待遍历完合并
合并方法
void attachLists(List* const * addedLists, uint32_t addedCount) { if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (!list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; } else { // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } } 复制代码
在合并时,大致有两种情况。一种是当前数组中为空,那就直接指向新添加的数组;另一种是当前数组中已经有数据:
- 先通过
realloc()
方法为原数组扩充空间 - 再将原数组的元素利用
memmove()
方法移动到新申请空间的后面 - 最后使用
memcpy()
方法把新数组复制到新空间的前面
方法覆盖
通过上面两段代码,可以发现两点:
- 分类中如果重写了方法,并不会覆盖原有方法
- 之所以在使用时出现“覆盖”原方法的现象,是因为在方法列表中分类中的方法被添加到了原方法的前面
- 如果某个类的多个分类都重写了同一个方法,那么,最后执行的是最后被遍历到的分类(也就是最后被添加到项目中的分类)中的方法
这篇关于Objective-C 之 Runtime 数据结构篇的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值