前言
提起弱引用,大家都知道它的作用:
(1)不会添加引用计数 (2)当所引用的对象释放后,引用者的指针自动置为nil
那么,围绕它背后的实现,是怎么样的呢?在许多公司面试时,都会问到这个问题。那么,今天就带大家一起分析一下weak引用是怎么实现的,希望能够搞清楚每一个细节。
Store as weak
当我们要weak引用一个对象,我们可以这么做:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
__weak NSObject *weakObj = obj;
}
}
创建了一个NSObject对象obj
,然后用weakObj
对obj做弱引用。
当我们对一个对象做weak引用的时候,其背后是通过runtime
来支持的。当把一个对象做weak引用时,会调用runtime方法objc_initWeak
:
objc_initWeak
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
该方法接受两个参数:
id *location
:__weak指针的地址,即例子中的weak指针取地址:&weakObj
。它是一个指针的地址。之所以要存储指针的地址,是因为最后我们要讲__weak指针指向的内容置为nil
,如果仅存储指针的话,是不能够完成这个功能的。id newObj
:所引用的对象。即例子中的obj
。
有一个返回值 id
: 会返回obj自身,但其值已经做了更改(isa_t中的weak_ref位置1),参见Objective-C runtime机制(5)——iOS 内存管理
objc_initWeak
实质是调用了storeWeak
方法。看这个方法的名字,就可以猜到是将weak引用存到某个地方,没错,实际上苹果就是这么做的。
storeWeak
storeWeak
方法有点长,这也是weak引用的核心实现部分。其实核心也就实现了两个功能:
-
将weak指针的地址location存入到obj对应的
weak_entry_t
的数组(链表)中,用于在obj析构时,通过该数组(链表)找到所有其weak指针引用,并将指针指向的地址(location)置为nil
。关于weak_entry_t
,在上一篇中已有介绍。 -
如果启用了isa优化,则将obj的
isa_t
的weakly_referenced
位置1。置位1的作用主要是为了标记obj被weak引用了,当dealloc时,runtime会根据weakly_referenced
标志位来判断是否需要查找obj对应的weak_entry_t
,并将引用置为nil
。
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating 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) { // 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil; // 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
}
if (haveNew) { // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; // 如果weak ptr不需要引用一个新obj,则newTable = nil
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
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()) // 如果cls还没有初始化,先初始化,再尝试设置weak
{
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