我们知道weak是弱引用,指向的对象的计数器不会加一,并且在该对象释放的时候会被置为nil。通常用来解决循环引用的问题,但是我们不仅要知其然,还要知其所以然。刚开始写文章,可能思路不是那么清晰,以下是笔者自己的一点粗略看法,如果文章中有什么错误或者不妥,欢迎大家指正。
1. 原理概括
苹果为了管理所有对象的计数器和weak指针,苹果创建了一个全局的哈希表,我们暂且叫它SideTables,里面装的是的名为SideTable的结构体。用对象的地址作为key,可以取出sideTable结构体,这个结构体用来管理引用计数和weak指针。
1.初始化一个weak对象时,runtime会调用一个objc_initWeak函数,初始化一个新的weak指针指向该对象的地址
2.在objc_initWeak函数中会继续调用objc_storeWeak函数,在这个过程是用来更新weak指针的指向,同时创建对应的弱引用表
3.在对象释放时,会调用clearDeallocating函数,这个函数会根据对象地址获取所有weak指针数组,然后遍历这个数组置为nil。最后把该条对象的记录从weak表中删除。
2. 创建weak指针并指向指定对象
1.初始化代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *p = [[NSObject alloc] init];
__weak NSObject *p1 = p;
}
return 0;
}
- runtime会在内部调用 objc_initWeak 函数
id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
可以看到,在这个函数中继续调用了 objc_storeWeak函数。
- 在objc_storeWeak函数中更改指针的指向和创建对应的弱引用表,具体实现如下图
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(HaveOld || HaveNew);
if (!HaveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (HaveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
从上图中我们可以看到 根据对象的地址,可以取出各自的SideTable结构体实例。前面说过,这个结构体管理者对象的计数器和weak指针。
这里我们先看下几个比较重要的结构体
SideTable
struct SideTable {
spinlock_t slock; // 因为操作对象的引用计数频率很快,因此系统在这里设置了一把自旋锁,保证是原子操作
RefcountMap refcnts; // 引用计数器哈希表,根据对象地址查找对象的引用计数
weak_table_t weak_table; // 维护weak指针的结构体
}
weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries; // 保存所有指向某一个对象的weak指针的一个数组,循环遍历此数组可找到指定对象的weak_entry_t结构体实例
size_t num_entries; // 用来维护数组的size始终在一个合理的大小
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被指向的对象的地址,前面循环遍历查找的时候就是判断目标地址是否和它相等。
union {
struct {
weak_referrer_t *referrers; // 可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// 只有4个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
解除旧对象和该weak指针的绑定
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
实际中就是遍历该对象的weak_entry_t结构体中的referrers数组,找到并删除这个weak指针
注册新对象和该weak指针的绑定
(objc_object *)weak_register_no_lock(&newTable->weak_table,(id)newObj, location, CrashIfDeallocating);
和解除操作相反,即将该weak指针添加到该对象weak_entry_t结构体中的referrers数组中。
3. 对象的释放过程
对象在释放时,会调用dealloc方法,dealloc方法中也进行了大量逻辑判断和其它处理,这里不展开讨论,最终会调用一个叫做sidetable_clearDeallocating()的函数
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
可以看到,如果有弱引用的话,会调用一个weak_clear_no_lock()的函数,这个就是把weak指针置为nil的关键点
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//1、拿到被销毁对象的指针
objc_object *referent = (objc_object *)referent_id;
//2、通过 指针 在weak_table中查找出对应的entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
//3、将所有的引用设置成nil
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
//3.1、如果弱引用超过4个则将referrers数组内的弱引用都置成nil。
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
//3.2、不超过4个则将inline_referrers数组内的弱引用都置成nil
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//循环设置所有的引用为nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
//4、从weak_table中移除entry
weak_entry_remove(weak_table, entry);
}
整个对象释放过程可以简单概括为:
- 根据释放对象的地址,在sideTables哈希表中获取到对应的sideTable结构体中的weak_table结构体实例,将自己和weak_table一起传入到weak_clear_no_lock(&table.weak_table, (id)this)中
- 在weak_clear_no_lock()函数中,通过weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);拿到weak_entry_t实例
- 遍历weak_entry_t中的weak指针数组,并全部置为nil
- 删除weak_table和引用计数表中的对应记录
参考资料:
iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-二
iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析