简介
本文主要记录 ObjC weak 原理。
主要内容如下:
- 存储
weak关系的数据结构为散列表,结构为hash(&obj) : [&p1, &p2, ...]。 weak变量,在指向的对象被销毁时,该变量会被置为 nil。- 「使用」
__weak修饰的变量,会被加入到 AutoReleasePool 中。
实现原理
以下注释来源于 objc-weak.h。
The weak table is a hash table governed by a single spin lock. ... So, in the hash table, indexed by the weakly referenced item, is a list of all locations where this address is currently being stored.
从以上描述中可知,weak table 是一个散列表,大致结构 [obj : [p1, p2]]。
但它是如何保存的?在对象被销毁时,又是如何处理呢?
试着从源码中寻找答案。
数据结构
关键的 weak_table_t
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 理解为指针即可
...
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer; // 说明保存的是指针本身的地址
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
而 weak_table_t 在 SideTable 之中。
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引用计数
weak_table_t weak_table;
...
}
store weak
众所周知,添加引用关系从设置变量开始。
static ALWAYS_INLINE
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
ptrdiff_t offset;
...
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
// 其他类型 ...
}
}
找到 objc_storeWeak(),其调用堆栈:
而 objc_storeWeak() 调用的 storeWeak() 才是关键逻辑:
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
storeWeak() 核心代码:
/**
* This function stores a new value into a __weak variable. It would
* be used anywhere a __weak variable is the target of an assignment.
*
* @param location The address of the weak pointer itself
* @param newObj The new object this weak ptr should now point to
*
* @return \e newObj
*/
static id
storeWeak(id *location, objc_object *newObj)
{
...
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);
...
// 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;
}
...
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
其中提到的 SideTables()
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
进而找到核心的处理函数 weak_register_no_lock()
核心源码:
// objc-weak.mm
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
...
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) { // 若该对象已有 weak 引用,会返回已有的 entry
append_referrer(entry, referrer);
}
else { // 一个新的对象被 weak 引用
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
/**
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer; // 说明保存的是指针本身的地址
return;
}
}
...
}
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
...
// 根据对象指针,计算出 index
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
...
}
从以上内容,可以知道 weak table 的数据结构为散列表:
- 根据对象的地址,计算出一个 hash 值作为 key。
- 将弱引用的变量地址,加入到一个列表中,后者作为 value。
比如以下代码,大致的结构为 hash(&obj) : [&p1, &p2, ...]
id obj = [NSObject new];
__weak id p1 = obj;
__weak id p2 = obj;
clear weak
官方文档
For ARC, we also keep track of whether an arbitrary object is being deallocated by briefly placing it in the table just prior to invoking dealloc, and removing it via objc_clear_deallocating just prior to memory reclamation.
其中提到 objc_clear_deallocating(),不断追溯,找到核心方法 weak_clear_no_lock(),其调用堆栈如下:
内部实现
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
...
// zero out references
weak_referrer_t *referrers;
size_t count;
...
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) { // 找到变量后,将其置为 nil
*referrer = nil;
}
else if (*referrer) {
...
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
从以上可知,在 -[NSObject dealloc] 中,会将对象所有的 weak 引用变量都置为 nil。
「使用」 __weak 修饰的变量,会被加入到 AutoReleasePool 中
来源于《Objective-C 高级编程》一书:
使用附有 __weak 修饰符的变量,即是使用注册到 AutoReleasePool 中的对象。
之前一直有误解,以为只要用 __weak 修饰变量,该变量就被加入到其中。
比如以下代码:
NSObject *o = ...;
__weak id weakPtr = o;
但翻看源码后,发现若只是声明,并不会加入,必须是「使用」。
// 声明只会调用此方法
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
// 「使用」__weak 修饰的变量,才会调用 objc_loadWeak()
/**
* This loads the object referenced by a weak pointer and returns it, after
* retaining and autoreleasing the object to ensure that it stays alive
* long enough for the caller to use it. This function would be used
* anywhere a __weak variable is used in an expression.
*
* @param location The weak pointer address
*
* @return The object pointed to by \e location, or \c nil if \e location is \c nil.
*/
id
objc_loadWeak(id *location)
{
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
从 objc_loadWeak() 的调用堆栈,可以看出来,必须是 get a weak ivar,它才会被加入到 AutoReleasePool 中。
而这样做的好处,就是延迟释放的时机。
id object_getIvar(id obj, Ivar ivar)
{
...
id *location = (id *)((char *)obj + offset);
if (memoryManagement == objc_ivar_memoryWeak) {
return objc_loadWeak(location); // 对于 weak 的变量
} else {
return *location;
}
}
所以书中的这句话,用以下代码表示会比较清晰些:
NSObject *o = ...;
__weak id weakPtr = o; // objc_initWeak()
[weakPtr description]; // objc_loadWeak() -> add to AutoReleasePool
[weakPtr description]; // 每使用一次,就会加入一次。
小结
回顾下主要内容:
weak table 是一个散列表,形式大概为 hash(&obj) : [&p1, &p2, ...]。
保存 weak 关系的过程中,主要涉及的函数为 storeWeak()、weak_register_no_lock()。
在指向的对象被销毁时,被 weak 修饰的变量会被置为 nil,涉及到的主要函数为 objc_clear_deallocating()。
每次「使用」 __weak 修饰的变量,就会被加入到 AutoReleasePool 中一次。