前言
weak 指针是 iOS 开发中一个非常基础的概念,在开发过程中我们经常使用它,它到底是怎么实现的?这篇文章将从 Runtime 源码入手,为你介绍 weak 指针的实现原理;让你知其然,更知其所以然。
weak 指针之编译期实现
当我们初始化一个 weak 指针时: __weak typeof(obj) weakObj = obj;
,编译器其实会把它们转换成类似这样的代码:objc_initWeak((void *)&weakObj, obj);
从上图的断点中我们也可以发现,weak 指针调用了 objc_initWeak
函数来完成初始化。在 Runtime 源码中我们可以找到 objc_initWeak
相关的实现细节。
本文引用的 Runtime 源码版本是 objc4-906,为了更方便阅读,我对代码样式和排版略作了修改以及删减了那些不影响主逻辑的冗余代码。
我在 这里 维护了一个可以直接运行调试的 Runtime 项目,方便大家在自己的电脑上直接调试。
weak 指针之运行时实现
与 weak 指针初始化相关的函数有以下 4 个:
// 初始化一个全新的 weak 指针。
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}
// 和前面的 objc_initWeak 一样,
// 但是,如果指向的对象(即 newObj)正在释放的话不要报错而是返回 nil。
id objc_initWeakOrNil(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DontCrashIfDeallocating>(location, (objc_object*)newObj);
}
// 释放 weak 指针当前指向的对象,并将它指向新对象(即 newObj)。
id objc_storeWeak(id *location, id newObj) {
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object *)newObj);
}
// 和 objc_storeWeak 一样。
// 但是,如果指向的对象(即 newObj)正在释放的话不要报错而是返回 nil。
id objc_initWeakOrNil(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DontCrashIfDeallocating>(location, (objc_object*)newObj);
}
关于函数 objc_initWeak 和 objc_storeWeak 的区别:
-
objc_initWeak
:用来初始化一个全新的 weak 指针。例如以下场景:NSObject *obj = ...; __weak id weakPtr = obj;// 在初始化的时候就指向了对象。
-
objc_storeWeak
:当 weak 指针被赋值的时候调用。例如以下场景:NSObject *obj = ...; __weak id weakPtr; weakPtr = obj;// 先初始化,后赋值。
从以上 4 个初始化函数不难发现,它们最终都调用了同一个函数 storeWeak
,区别就是传递给函数的模板参数略有不同。
weak 指针的初始化细节:storeWeak
storeWeak
函数的相关代码整理后如下所示:
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
/*
location: weak 指针的内存地址,即 `__weak id weakPtr;` 中的 &weakPtr。
newObj: weak 指针要指向的对象,即 `__weak id weakPtr = obj;` 中的 obj。
*/
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 当 newObj 的类型还未初始化的时候会用到。
Class previouslyInitializedClass = nil;
// 保存 weak 指针当前指向的对象。
id oldObj;
// SideTable 是用来存储弱引用关联的 1 个数据结构,后面会单独讲。
SideTable *oldTable;
SideTable *newTable;
retry:
if (haveOld) {
oldObj = *location;
// 调用全局函数 SideTables 获取 oldObj 对应的 SideTable 对象。
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 该函数不是线程安全的,所以执行到这里的时候,
// weak 指针的值有可能被其它线程修改了,
// 如果是的话就跳转到 retry 重新获取数据。
if (haveOld && *location != oldObj) {
goto retry;
}
if (haveNew && newObj) {
Class cls = newObj->getIsa();
/*
检查 newObj 的类型是否已经完成初始化。
一般都不会发生这种情况,除非你在 +initialize 中对该对象进行弱引用,
例如以下场景:
@implementation Person
+ (void)initialize {
Person *obj = [[self alloc] init];
// 此时 Person 类还未完成初始化操作。
__weak typeof(obj) weakObj = obj;
}
@end
*/
if (cls != previouslyInitializedClass &&
!cls->isInitialized()) {
// 对 newObj 类型进行初始化操作,然后跳转 retry 重新获取数据。
class_initialize(cls, newObj);
previouslyInitializedClass = cls;
goto retry;
}
}
if (haveOld) {
// 如果 weak 指针当前持有了一个对象,先解除与这个对象的弱引用关联。
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
// 将 weak 指针与新对象建立弱引用关联。
newObj = weak_register_no_lock(&newTable->weak_table,
(id)newObj,
location,
/*
之前在初始化函数那里提到过带 OrNil 后缀和不带后缀的区别,
重点就在这里,带后缀的函数这里会传递 ReturnNilIfDeallocating,
不带后缀的会传递 CrashIfDeallocating。
函数内部会检查 newObj 是否正在释放过程中,
如果是的话就会使用这个参数来决定怎么处理。
*/
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// 修改 newObj 对象的 isa 中关于是否有弱引用的标记。
if (!_objc_isTaggedPointerOrNil(newObj)) {
newObj->setWeaklyReferenced_nolock();
}
// 将 weak 指针指向 newObj。
*location = (id)newObj;
}
/*
这个函数我翻了往年的 runtime 源码,发现是从 objc4-818.2 开始引入的。
它的内部逻辑大致如下:
1. 检查对象是否实现了手动引用计数。
2. 如果支持的话,再判断对象是否有实例方法 _setWeaklyReferenced 的实现,如果有的话就调用。
*/
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
这个函数就是 weak 指针初始化的最终函数。从函数中不难发现,它主要就干了 2 件事:
- 调用
weak_unregister_no_lock
:将 weak 指针与当前对象解除弱引用关联。 - 调用
weak_register_no_lock
:将 weak 指针与新对象建立弱引用关联。
weak 指针解除关联的细节:weak_unregister_no_lock
void
weak_unregister_no_lock(weak_table_t *weak_table,
id referent_id,
id *referrer_id) {
// weak 指针当前指向的对象,即 `__weak id weakPtr = obj;` 中的 obj。
objc_object *referent = (objc_object *)referent_id;
// weak 指针的内存地址,即 `__weak id weakPtr;` 中的 &weakPtr。
objc_object **referrer = (objc_object **)referrer_id;
// referent 对象的弱引用表。
weak_entry_t *entry;
if (!referent) return;
// 从 weak_table 中取出 referent 对应的弱引用表。
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 从弱引用表中移除 weak 指针的地址,即移除 referrer。
remove_referrer(entry, referrer);
// 移除后检查一下表是否为空,
// 如果是的话就删除这个表。
bool empty = true;
if (entry->out_of_line() &&
entry->num_refs != 0) {
empty = false;
} else {
for (size_t i = 0; i < 4; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
// 从弱引用表中获取某个对象对应的那张表数据。
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) {
// 下面的逻辑是一段典型的哈希表实现细节,关于哈希表的实现原理请自行了解。
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
// 从弱引用表中移除指定的 weak 指针。
static void
remove_referrer(weak_entry_t *entry, objc_object **old_referrer) {
// 系统在存储弱指针数据的时候会采用 2 套方案。
// 如果数据量比较小就会使用静态数组存放。
// 这里的逻辑就是判断,如果是静态数组方案就执行这段逻辑。
if (!entry->out_of_line()) {
for (size_t i = 0; i < 4; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
return;
}
// 如果是动态数组存储的话,就执行这段逻辑。
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
// 弱引用表中没有这个弱指针数据,不需要移除。
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
// 从弱引用表中移除某张表。
static void
weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) {
// 如果是动态数组方案的话,释放创建的动态数组。
if (entry->out_of_line()) free(entry->referrers);
memset(entry, 0, sizeof(*entry));
weak_table->num_entries--;
// 如果需要的话,对弱引用表进行缩容。
weak_compact_maybe(weak_table);
}
代码稍微有点多,但整体的逻辑比较清晰。weak_unregister_no_lock
函数中主要做了 3 件事:
- 调用
weak_entry_for_referent
从弱引用表中获取指定的那张表数据。 - 调用
remove_referrer
从表中移除弱指针(即 referrer)。 - 检查表是否为空,是的话就调用
weak_entry_remove
从 weak_table 中移除这张表。
移除弱引用关联,本质上就是从 weak_table 中找到对象对应的弱引用数组,然后从数组中找到需要移除的 weak 指针并将其置空。weak_table 其实就是一个哈希表,关于哈希表的实现原理请自行了解。
weak 指针建立关联的细节:weak_register_no_lock
id
weak_register_no_lock(weak_table_t *weak_table,
id referent_id,
id *referrer_id,
WeakRegisterDeallocatingOptions deallocatingOptions) {
// weak 指针当前指向的对象,即 `__weak id weakPtr = obj;` 中的 obj。
objc_object *referent = (objc_object *)referent_id;
// weak 指针的内存地址,即 `__weak id weakPtr;` 中的 &weakPtr。
objc_object **referrer = (objc_object **)referrer_id;
// 这里是确保 weak 指针要指向的那个对象是有效的(即没有正在释放)。
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
} else {
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
referent->getIsa());
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
// 如果正在释放的话,根据参数 deallocatingOptions 来决定执行什么操作。
if (deallocating) {
if (deallocatingOptions == CrashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
}
weak_entry_t *entry;
// weak_entry_for_referent 函数在前面的 weak_unregister_no_lock 中已解释过。
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
} else {
// 找不到对应的表,创建一个新表。
weak_entry_t new_entry(referent, referrer);
// 如果需要的话,对 weak_table 进行扩容。
weak_grow_maybe(weak_table);
// 将新表插入到 weak_table 中。
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
// 向弱引用表中添加一个 weak 指针。
static void
append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
// 如果当前采用的是静态数组方案就执行这段逻辑。
if (!entry->out_of_line()) {
// 尝试在静态数组中插入数据。
for (size_t i = 0; i < 4; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 来到这里说明静态数组已经存满了,后面的逻辑是把静态数组转为动态数组存储。
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(4, sizeof(weak_referrer_t));
for (size_t i = 0; i < 4; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = 4;
entry->out_of_line_ness = 2;
entry->mask = 4 - 1;
entry->max_hash_displacement = 0;
}
// 如果数组的元素数量大于等于总容量的 3/4,则对数组进行扩容并插入新数据。
if (entry->num_refs >= (entry->mask + 1) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
// hash_displacement 保存的是此次遇到的哈希冲突次数,
// 之所以要保存这个值,是因为取值的时候也会遇到哈希冲突,
// 此时需要和这个值进行比较,如果大于这个值的话就说明哈希表中没有要找的数据。
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
建立关联和解除关联的逻辑相似,本质都是通过 weak_entry_for_referent
获取对应的表。然后在通过 weak 指针进行一系列哈希运算,从而拿到要添加/要删除的数组索引,最终对数组元素进行添加/删除操作。
截止到这里,关于 weak 指针的整个初始化过程已经全部讲完了。如果你还想了解更多细节的话可以继续阅读后面的内容。
深入剖析 SideTable 类型
SideTable 是用来存储弱引用数据和引用计数的一个数据结构,由于这篇文章只涉及弱引用相关问题,所以在后面的源码中我会特意去掉与文章不相关的内容,如果你想了解全部细节的话请阅读源码。
struct SideTable {
// 你可以把它理解成一个哈希表,
// key 是对象;value 是对象对应的弱引用表数据。
weak_table_t weak_table;
};
struct weak_table_t {
/*
这是一个数组,数组中的每个元素是一个 weak_entry_t 对象。
weak_entry_t 里存储了弱引用的相关数据。
*/
weak_entry_t *weak_entries;
// 数组 weak_entries 的元素数量。
size_t num_entries;
// 数组 weak_entries 的长度减一,求数组索引时需要用到。
uintptr_t mask;
/*
记录在存储元素时遇到的最大的哈希冲突次数。
在查找元素的时候会被用到,
如果查找过程中哈希冲突次数大于这个值就说明哈希表中没有要找的数据。
*/
uintptr_t max_hash_displacement;
};
// 真正用来存储弱引用数据的结构。
struct weak_entry_t {
// weak 指针所指向的那个对象(类似哈希表中的 key)。
id referent;
/*
从以下代码可以发现:系统内部在存储弱引用数据的时候有 2 套方案,
如果数据比较少就使用静态数组 inline_referrers 存储;
如果数据比较多就使用动态数组 referrers 存储。
*/
union {
struct {
// 用来存储弱引用的动态数组。
id *referrers;
/*
一个标记位,如果这个值是 2 的话就表示使用的是动态数组。
因为 objc 对象的内存地址的最后一位只会是 0x8 或 0x0,
即:如果采用的是静态数组方案的话,这个位置的数据绝对是 0。
*/
uintptr_t out_of_line_ness : 2;
// 数组 referrers 的元素数量。
uintptr_t num_refs : 62;
// 数组 referrers 的长度减一,和 weak_table_t 中的 mask 功能一样。
uintptr_t mask;
// 和 weak_table_t 中的 max_hash_displacement 功能一样。
uintptr_t max_hash_displacement;
};
struct {
// 用来存储弱引用的静态数组。
id inline_referrers[4];
};
};
};
你可以把 SideTable 理解成一个获取弱引用表的入口,weak_table 类似一个大哈希表,这个哈希表的 key 是 objc 对象,value 是对应的弱引用数组。关于 weak 指针的操作,例如解除关联和建立关联,其实就是从 weak_table 中获取其对应的弱引用数组,然后从这个数组中移除或添加对应的弱指针地址。
weak 指针自动赋值 nil 的实现细节:weak_clear_no_lock
众所周知,weak 指针在对象被释放之后会自动指向 nil,那么它到底是如何实现的呢?
关于释放流程的函数调用顺序这里就不具体展开了,在 objc4-906 版本中,其函数调用顺序如下:dealloc => _objc_rootDealloc => rootDealloc => object_dispose => objc_destructInstance => clearDeallocating => clearDeallocating_slow => weak_clear_no_lock,我们重点看一下最后一个函数 weak_clear_no_lock
的实现细节。
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {
// 正在释放的对象。
objc_object *referent = (objc_object *)referent_id;
// 获取当前对象对应的弱引用表。
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
weak_referrer_t *referrers;
size_t count;
// 获取弱指针数组和数组容量。
if (entry->out_of_line()) {
referrers = entry->referrers;
count = (entry->mask ? entry->mask + 1 : 0);
} else {
referrers = entry->inline_referrers;
count = 4;
}
// 重点:系统会遍历弱指针数组中的每个地址,并将它们赋值为 nil。
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {// 等价于 weakSelf == self(self 表示当前正在释放的对象)
*referrer = nil;// 等价于 weakSelf = nil;
} else if (*referrer) {
REPORT_WEAK_ERROR("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak().",
referrer, (void*)*referrer, (void*)referent);
}
}
}
// 从弱引用表中移除这张表。
weak_entry_remove(weak_table, entry);
}
从源码中不难发现,系统会在对象释放的时候,获取其对应的弱引用表,然后遍历这个表中的 weak 指针并将其赋值为 nil。
到此为止,关于 weak 指针的所有内容就讲完了。
总结
在 runtime 初始化的时候,会调用一个全局函数 side_tables_init
初始化一个全局数组,数组的元素是 SideTable 对象,可以通过全局函数 SideTables
拿到这个数组以及对象对应的 SideTable 对象。
SideTable 中有一个变量 weak_table,你可以将它理解成一个哈希表,哈希表的 key 是对象,value 是一个数组,数组中的元素就是指向这个 key 的 weak 指针地址。
weak 指针的初始化操作就是拿到这个对象对应的弱指针数组,然后往数组里面把 weak 指针的内存地址添加进去。
如果 weak 指针需要指向别的对象,需要拿到旧对象对应的弱指针数组并将数组中存放 weak 指针的那个位置置空,然后拿到新对象对应的弱指针数组并将 weak 指针添加进去。
如果对象释放了,就拿到这个对象对应的弱指针数组并挨个将里面的 weak 指针赋值为 nil。
为了加深自己的理解,我模仿系统的实现写了一个示例项目 WeakPointer,我在这个项目里还给分类属性也支持了 weak 特性,感兴趣的同学可以参考一下。
关于 weak 指针的一些疑问与解答
为什么不能给 Category 添加 weak 属性?
我们一般是这样初始化一个 weak 指针:__weak id weakPtr = obj;
,从源码中我们知道编译器会把代码转换成这样:objc_storeWeak((void *)&weakPtr, obj);
。
从这里可以发现,要实现 weak 特性,你必须能拿到 obj 对象和 weak 指针的内存地址,而 Category 中的属性是依靠 runtime 中的 objc_setAssociatedObject
和 objc_getAssociatedObject
这 2 个函数来实现的,我们拿不到 weak 指针的内存地址,故而无法给 Category 的属性支持 weak 特性。
类的属性之所以支持 weak 特性,是因为编译器能拿到这个属性的成员变量的地址(即 weak 指针的内存地址)。
如果你一定要给 Category 添加 weak 属性的话,有以下 2 个思路(建议选择第 2 个):
-
参考我的这个项目 WeakPointer 模仿系统的实现手动维护一个弱引用表来支持 Category 的 weak 属性。
-
创建一个中间类,给中间类声明一个 weak 属性,Category 的属性强引用这个中间类,中间类的 weak 属性指向真正的对象。
示例代码:
@interface WeakTarget : NSObject @property (nonatomic, weak) id weakObj; @end @interface Person(Category) @property (nonatomic, weak) id weakObj; @end @implementation Person (Category) - (void)setWeakObj:(NSObject *)obj { WeakTarget *target = [[WeakTarget alloc] init]; target.weakObj = obj; objc_setAssociatedObject(self, @selector(weakObj), target, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)weakObj { WeakTarget *target = objc_getAssociatedObject(self, @selector(weakObj)); return target.weakObj; } @end @implementation WeakTarget @end
为什么在 block 中不能使用 weak 指针访问其成员变量。
这是我在项目中实际遇到的一个问题,伪代码如下:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
weakSelf->_propertyName;// 使用 weak 指针直接访问成员变量。
});
上面的代码无法通过编译,如果改成这样就没问题了:weakSelf.propertyName;
。
众所周知,在 objc 里访问属性最终还是会访问成员变量。那为什么访问属性就正常,访问成员变量就会报错呢?
之所以会这样,是因为现在的编译器比较智能,考虑的比较多。weakSelf 在运行时有可能为 nil 从而导致崩溃,编译器认为这样的代码不安全所以报错。但是使用 weakSelf 访问属性是安全的,因为访问属性实际上是调用了属性的 get/set 方法,在 objc 里对 nil 调用方法是不会导致异常。
上面的报错代码可以改成以下代码来解决编译报错:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (weakSelf) {// 这一步很重要,因为这里的 weak 指针有可能已经是 nil 了。
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf->_propertyName;
}
});
为什么在对象没有弱引用时也会执行 weak_clear_no_lock
在研究 weak 指针自动赋值 nil 的过程中,我发现,对象只要曾经被 weak 指针指向过,在对象释放的时候即使没有指向它的 weak 指针,也会执行到 weak_clear_no_lock 函数。
在 storeWeak 函数中会调用这行代码设置对象被 weak 指针指向的标记:newObj->setWeaklyReferenced_nolock();
。
但是,当对象没有任何 weak 指针指向时,weak_unregister_no_lock 函数中并没有调用相关函数将标记设置为 false。
这会导致在对象释放的时候,即调用到 rootDealloc 函数时无法执行快速释放逻辑。
inline void
objc_object::rootDealloc() {
if (isTaggedPointer()) return;
if (fastpath(isa().nonpointer &&
// 如果对象曾经被 weak 指针指向过,即使现在没有了,weakly_referenced 也是 true。
!isa().weakly_referenced &&
!isa().has_assoc &&
!isa().getClass(false)->hasCxxDtor() &&
!isa().has_sidetable_rc)) {
free(this);
} else {
object_dispose((id)this);
}
}
我也不太清楚为什么这么做?如果你知道其中的具体细节的话,还请留言告知。