在Objective-C中,一般为了解决循环引用的问题,我们会使用weak 修饰,使得一方不会持有另一方,解决循环引用的问题.
今天就从Objective-C的源码中看一下weak是怎么被实现的,在NSObject.mm文件中可以找到一个这样的函数
/** * 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(location, (objc_object*)newObj);
}
id objc_initWeakOrNil(id *location, id newObj){
if (!newObj) { *location = nil; return nil; }
return storeWeak (location, (objc_object*)newObj);
}
从注释中可以看到weak变量会调用这个两个方法其中一个进行初始化.然后我们接着看后面的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
id objc_storeWeak(id *location, id newObj)
从注释中可以很明显看出这个方法就是用来存储weak变量的
下面是方法的具体实现:
// If HaveOld is true, the variable has an existing value
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
// assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
// deallocating or newObj's class does not support weak references.
// If CrashIfDeallocating is false, nil is stored instead.
id objc_storeWeak(id *location, id newObj)
{
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:
oldObj = *location;
//获取SideTable
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
......
if(*location != oldObj) {
OSSpinLockUnlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if(lock1 != lock2) OSSpinLockUnlock(lock2);
#endif
goto retry;
}
if(oldObj) {
//清空老值
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if(newObj) {
//存储新值
newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
// weak_register_no_lock returns NULL if weak store should be rejected
}
// Do not set *location anywhere else. That would introduce a race.
*location = newObj;
......
returnnewObj;
}
首先看注释得到的答案是如果这个变量有老值,会先清除,如果是新值会存储起来(这个值可以为nil),如果新值正在释放或者是新值得类不支持weak,则会存储一个nil代替.
首先会根据要存储的对象去获取SideTable,先分析一下SideTable的代码
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
//存储weak_entry的hashTable
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
static void lockTwo(SideTable *lock1, SideTable *lock2);
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
可以看到SideTable内部有一个成员变量weak_table,不难猜出这个weak_table是用来存储weak_entry的,接下来继续看weak_table的结构
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;
};
可以看到weak_table_t就是一个内部包含一个weak_entries指针的结构体,继续看weak_entry_t的结构
/**
* The internal structure stored in the weak references table.
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
* is instead a small inline array.
*/
struct weak_entry_t {
//weak变量指向的对象
DisguisedPtr referent;
//选择是用链表结构还是一个小数组存储所有指向该对象的弱引用指针,weak_referrer_t是弱引用指针的内存地址
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
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_entry_t用来存储一个对象的weak引用的信息.weak_entry_t会保存一个HashSet,这个HashSet会存储一个对象的弱引用的指针
所以现在可以得到结论了,weak变量初始化后保存在weak_entry_t这个结构体中,weak_entry_t会存储weak指向的对象,同时weak_entry_t 的referrers会存储这个对象的所有weak指针,weak_entry_t会储存在weak_table_t这个结构体的weak_entries成员中,weak_table这个结构也是一个标准的hashTab的实现.weak_entry_t存储在weak_table事,是以weak_entry_t的referent(即weak指针指向的对象)的hash值为key,weak_entry为value存储在weak_table中的.
然后就是weak变量如何被释放掉,在NSObject.mm文件中我可以找到如下函数:
/** * Destroys the relationship between a weak pointer
and the object it is referencing in the internal weak table. If the weak pointer is not referencing anything, there is no need to edit the weak table. This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.)
void objc_destroyWeak(id *location){
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil);
}
在注释中写的就是销毁weak变量指针和对象的联系,这个方法仍旧调用了stroreWeak,不过传入的参数可以看到模板传入的是DoHaveOld, DontHaveNew, DontCrashIfDeallocating ,实参传入的是location(指针的内存地址), nil,这时我们在回头看storeWeak方法,我们可以看到这种情况下storeWeak会调用weak_unregister_no_lock方法,我们继续看weak_unregister_no_lock方法,在objc-weak.mm中我们可以看到weak_unregister_no_lock的实现:
**
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
//weak变量指向的对象
objc_object *referent = (objc_object *)referent_id;
//weak变量的指针地址
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
//通过weak_entry_t的referent找到weak_entry然后移除对应的referrer,
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
//判断该对象的所有weak指针是否为空
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
//如果该对象的所有weak指针为空,则代表没有任何weak指针指向这个对象,从weak_table中移除掉这个entry
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
从注释中我们可以得到这个方法就是用来注销已经存在的若引用指针,这个方法调用的时机是weak变量的referrer(weak变量的指针)将要消失的时候.
从代码的实现中我们可以看出,方法内部首先移除的是weak_entry的referrer,如果weak_entry的referrers为空,再从weak_table中移除weak_entry.
首先我们要看remove_referrer这个方法:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
//weak_entry->out_of_line()标记weak_entry->referrers的结构是数组还是hashTable
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
//如果找到weak_entry的referrer,就将其置nil
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
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) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
//找到weak变量的指针,将其置nil
entry->referrers[index] = nil;
entry->num_refs--;
}
从其代码实现,我们就可以很明白的理解为什么weak变量释放时weak指针会自动置nil了
接下来我们继续看weak_entry_remove方法:
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//根据weak_table的长度动态调整weak_table
weak_compact_maybe(weak_table);
}
看注释发现,这个方法就是把weak_entry从weak_table中移除.看代码的实现,它释放了weak_entry的referrent,同时将整个weak_entry内存全部置为0,之后将weak_table的长度减一.
以上就完成了将weak指针和其对象的关联销毁的全部过程.
当然还有一种情况就是weak指针指向的对象释放后,是如何处理的.释放对象的基本流程在NSObject.mm文件中也可以看到:
调用objc_release
若对象的引用计数为0,执行dealloc
在dealloc中,调用了_objc_rootDealloc函数
在_objc_rootDealloc中,调用了object_dispose函数
调用objc_destructInstance
最后调用objc_clear_deallocating
我们只需关心objc_clear_deallocating这个方法:
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
从其实现代码可以看到,针对被weak指向的对象,会调用clearDeallocating_slow方法,接下来可以看clearDeallocating_slow其实现代码:
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
在实现中我们可以看到当对象是若引用的时候会调用weak_clear_no_lock方法,所以我们继续看weak_clear_no_lock的实现,在objc-weak.mm中可以发现如下代码,这个方法提供了在dealloc时,一次性释放所有的weak变量
/**
* 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;
//从weak_table中找到weak_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;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
//判断weak_entry的referrers的结构
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//释放weak_entry的 referrers,将其置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();
}
}
}
//从
weak_entry_remove(weak_table, entry);
}
从注释中可以看出,这个方法在dealloc的时候会被调用,当weak指针指向的对象被释放的时候,会将改对象的所有weak指针全部置nil,并将该对象的weak_entry从weak_table中移除.
网友评论