弱引用 - weak
本文使用的是objc4-706
打开NSObject.mm
,首先看到的就是跟弱引用相关的代码。
我们先看一下几个宏定义
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
复制代码
其中:
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
表示第一位是弱引用标志位,#define SIDE_TABLE_DEALLOCATING (1UL<<1)
表示第二位是析构标志位#define SIDE_TABLE_RC_ONE (1UL<<2)
表示第三位开始是存储引用计数数值#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
表示最后一位是引用计数数值的溢出表示位
接下来,就是跟弱引用中最重要的部分,存放着弱引用信息的SideTable
这个hash table
struct SideTable {
spinlock_t slock; // 自旋锁,防止资源竞争
RefcountMap refcnts; // 保存引用计数的散列表
weak_table_t weak_table; // 全局的弱引用散列表
...
}
复制代码
从代码中,我们可以看到SideTable
是一个结构体,有一个自旋锁和两个保存弱引用变量相关信息的散列表。
-
RefcountMap
是一个简称,完整的类型是typedef objc:DenseMap<DisguisedPtr<objc_object>, size_t, true> RefcountMap
这样一个散列表,其中key为DisguisedPtr<objc_object>
, 表示指向对象的指针(location),value为size_t
的数值,表示当前变量被引用的次数。 -
weak_table_t
也是一个散列表,key是弱引用的对象(obj),value是weak_entry_t
这个结构体。#if __LP64__ #define PTR_MINUS_2 62 #else #define PTR_MINUS_2 30 #endif /** * 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. */ #define WEAK_INLINE_COUNT 4 // out_of_line_ness field overlaps with the low two bits of inline_referrers[1]. // inline_referrers[1] is a DisguisedPtr of a pointer-aligned address. // The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 // (disguised nil or 0x80..00) or 0b11 (any other address). // Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state. #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr<objc_object> referent; 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]; }; }; ... }; 复制代码
weak_entry_t
存放了DisguisedPtr<objc_object> referent;
被引用的对象和weak_referrer_t
类型的weak指针。从上面可以看到,weak_entry_t
有两种方式存储指针,当out_of_line_nes != REFERRERS_OUT_OF_LINE
时,指针会存储在数组当中,否则的话,是存储在一个哈希集合当中。
好了,看完weak
相关的数据结构,接下来我们看看整个流程是怎样的:
在main.m文件中添加下面的代码:
int main(int argc, char * argv[]) {
@autoreleasepool {
__strong NSString *string = @"Hello";
{
__weak id weakObj = string;
NSLog(@"%@", weakObj);
weakObj = @"Hei";
NSLog(@"%@", weakObj);
}
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
将main.m文件编译为汇编代码:
根据得到的汇编代码,能够整理出一个weak变量的实现大概是:
{
_objc_initWeak
_objc_loadWeakRetained
_objc_release
_objc_storeWeak
_objc_loadWeakRetained
_objc_release
_objc_destoryWeak
}
复制代码
可以看到,初始化和销毁分别是_objc_initWeak
和objc_destoryWeak
函数,而每一次调用weak变量的时候,都会出现一对_objc_loadWeakRetained
和_objc_release
来对引用计数进行+1和-1。在对其进行赋新值的时候,调用的是_objc_storeWeak
函数
API分析
-
id objc_initWeak(id *location, id newObj)
: 初始化一个weak指针,指向某个对象。location
代表的是__weak
指针的地址,newObj
则是对象。 -
id objc_loadWeakRetained(id *location)
: 根据指针地址在SideTable中的RefcountMap中找到对应的值进行+1(objc_object::sidetable_tryRetain())RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { table.refcnts[this] = SIDE_TABLE_RC_ONE; } else if (it->second & SIDE_TABLE_DEALLOCATING) { result = false; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second += SIDE_TABLE_RC_ONE; } 复制代码
-
void objc_release(id obj) { [obj release]; }
这个方法就跟上面做的相反,是将引用计数-1(uintptr_t objc_object::sidetable_release(bool performDealloc)):RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { // 没得减的话,就进行销毁 do_dealloc = true; table.refcnts[this] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) { // 已经是销毁状态的话,不做任何更改 do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second -= SIDE_TABLE_RC_ONE; } 复制代码
-
void objc_destroyWeak(id *location)
销毁弱引用对象,最后跟initWeak
一样,操作的还是storeWeak
这个方法。 -
static id storeWeak(id *location, objc_object *newObj)
:(敲黑板,重点了)更新弱引用变量的值。上面说过了,weak变量的计数信息等都是存储在SideTable
中的,storeWeak
就是用于更新SideTable
里面变量的信息的。复制代码
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;
复制代码
}
根据源码,我们能了解到,weak变量的运作流程大概是:
复制代码
- storeWeak
- if HaveOld
-
oldTable = &SideTables()[oldObj]; // 如果存在旧值的话,从全局散列表中数组中获取存储着旧值信息的散列表 复制代码
- if HaveNeew
-
newTable = &SideTables()[newObj]; // 要设置新值的话,则取出新值的散列表 复制代码
- weak_unregister_no_lock(&oldTable->weak_table, oldObj, location) // 将location从旧的weak_table中移除
- weak_register_no_lock(&newTable->weak_table, id(newObj), location, CrashIfDeallocationg) // 将新的obj和location添加到新的weak_table当中(sidetable_relaese)
- newObj->setWeaklyReferenced_nolock(); // 将sidetable中的refcnts的对应的标志位设置为1,表示存在弱引用
***
Note:
看到一些之前的帖子,有介绍关于`TaggedPointer`的知识,不过当前版本中,Apple好像已经不再支持非64位环境的了,所有`isTaggedPointer()`都是返回了`false`
> 有些对象如果支持使用 TaggedPointer,苹果会直接将其指针值作为引用计数返回;如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么“一些”对象会使用其 isa 指针的一部分空间来存储它的引用计数;否则 Runtime 会使用一张散列表来管理引用计数
***
---
1. [Objective-C Automatic Reference Counting (ARC)](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-initweak)
2. [Objective-C 引用计数原理](http://yulingtianxia.com/blog/2015/12/06/The-Principle-of-Refenrence-Counting/)复制代码