__weak
修饰符的特点:使用__weak
修饰符的指针在其指向的对象被废弃时,会重新指向nil
。
为了实现该特点,ObjC
为每个被__weak
引用的对象维护了一张弱引用表weak_table
,记录了所有指向该对象的__weak
指针的地址。当对象被销毁时,ObjC
会将弱引用表中的所有__weak
指针重新指向nil,并清除这些弱引用记录。
__weak
修饰的指针可能在使用过程中重新指向其它对象,这将导致弱引用关系变化。为此,llvm
会根据__weak
指针的使用方式自动补充函数调用代码,这些补充的代码会将最新的引用关系同步到弱引用表weak_table
中。
__weak
修饰的指针可能在某些操作过程的中途被释放了,为了避免这种情况,ObjC
会判断在某些特殊场景将__weak
修饰的指针进行retain
操作,并注册到autoreleasepool
中,由autoreleasepool
控制其释放。这段过程的代码也是由llvm
自动补充的。
__weak
修饰符带来了一定的性能开销,所以建议只在避免循环引用的场景中使用它。
下面来看一段常见的__weak
修饰符代码。
NSObject *obj = [[NSObject alloc] init];
id __weak weakObj = obj;
通过Clang的方法把__weak编译成C++
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) weakObj = obj;
可以看到,__weak
对应的类型属性为__attribute__((objc_ownership(weak)))
。
__attribute__
可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute),这里__attribute__
给变量weakObj设置的是变量属性,objc_ownership(weak)
代表以weak弱引用的方式管理对象所有权。
在__weak
代码行加上断点并跟踪汇编信息,可以发现底层调用了objc_initWeak()
函数。
另一种方式是转换成llvm中间代码,可以看到
llvm.objc.initWeak
调用,其中llvm是编译器,objc.initWeak
会自动转换成objc_initWeak()
函数调用。
xcrun -sdk iphoneos clang -S -arch arm64 -fobjc-arc -emit-llvm main.m
%15 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %14, i8* %13)
%16 = bitcast i8* %15 to %1*
store %1* %16, %1** %6, align 8
%17 = load %1*, %1** %6, align 8
%18 = bitcast %1* %17 to i8*
%19 = call i8* @llvm.objc.initWeak(i8** %7, i8* %18) #3
在objc4
源码中可以看到objc_initWeak()
的内部实现。
/**
* 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)
{
// 查看对象是为nil。若对象为nil,weak指针也指向nil,并返回nil
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
objc_initWeak()
传入参数分别是weak指针地址(二级指针,可以修改weak指针指向)和指向的对象,表示用对象初始化weak指针。所以上面的代码可以理解为如下。
NSObject *obj = [[NSObject alloc] init];
id __weak obj1;
objc_initWeak(&obj1, obj);
objc_initWeak()
内部调用了关键函数storeWeak()
,并对template
传递DontHaveOld, DoHaveNew, DoCrashIfDeallocating
。下面是template的定义和描述。
// Update a weak variable.
// 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.
// 如果HaveOld == true,代表weak指针之前已经指向了一个对象
// 如果haveNew == true,代表weak指针需要指向一个新的对象
// 如果crashIfDeallocating == true,代表如果newObj正在deallocating或者newObj的类不支持weak弱引用,程序将会crash
// 如果crashIfDeallocating == false,将存储的数据置为nil
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template
storeWeak()
的作用是更新弱引用表。storeWeak()
通过template配置操作信息。
objc_initWeak()
设置的template是 false,true,true,用对象初始化weak指针,objc_initWeak()的描述Initialize a fresh weak pointer to some object location.
,其中的fresh
,代表该weak指针是未被设置过的。对于未初始化的weak指针,HaveOld设置为false。需要设置新值,HaveNew为true。
storeWeak()
函数主要依次做了以下事情:
- 如果有旧对象oldObj,从全局SideTables获取到旧对象关联的SideTable的指针,赋值给oldTable。
相关代码:oldTable = &SideTables()[oldObj];
- 如果有新对象newObj,在全局SideTables创建新对象关联的SideTable,并赋值给newTable。
相关代码:newTable = &SideTables()[newObj];
- 如果有旧对象oldObj。从oldTable的weak_table(弱引用表)清除与弱指针的地址location的关联。
相关代码:weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
- 如果有新对象newObj。在newTable的weak_table(弱引用表)增加弱指针的地址location。
相关代码:newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
- 如果有新对象newObj。在newTable的refcnts(引用计数)中有一位是标记是否存在弱引用关系,将其标记为1。
相关代码:newObj->setWeaklyReferenced_nolock();
- 如果有新对象newObj。将弱指针*location指向新对象newObj。
相关代码:*location = (id)newObj;
关于SideTable的分析:https://www.jianshu.com/p/9c66360c0f24
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(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(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(oldTable, newTable);
class_initialize(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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
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(oldTable, newTable);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
上面讲解了给一个刚创建的weak指针赋值,下面尝试下另一种场景,给一个已经有值的weak指针再次赋值。
NSObject *obj = [[NSObject alloc] init];
id __weak weakObj = obj;
NSObject *obj2 = [[NSObject alloc] init];
weakObj = obj2;
将断点设置在第四行代码,并跟踪汇编信息。
可以看到这次和上次不同,调用的是objc_storeWeak()
函数。
在
objc4
中查看objc_storeWeak
源码。内部和objc_initWeak()
一样,同样调用了关键函数storeWeak()
,但是传参不同(true,true,true)
,这很好理解,这次weakObj已经存在旧值,所以第一个传参为true。
/**
* 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
*/
id objc_storeWeak(id *location, id newObj)
{
return storeWeak
(location, (objc_object *)newObj);
}
下面测试另一种情况,将代码改成如下。
{
NSObject *obj = [[NSObject alloc] init];
id __weak weakObj = obj;
}
在第四行}上设置断点,然后跟踪汇编信息。
可以看到,代码执行完objc_initWeak()
函数后,即将执行objc_destroyWeak()
函数。
在
objc_destroyWeak()
函数内部,同样调用storeWeak()
函数,传参为(true,false,true),此时不存在新值。
/**
* 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.)
*
* @param location The weak pointer address.
*/
void objc_destroyWeak(id *location)
{
(void)storeWeak
(location, nil);
}
对上面的分析进行归纳,主要是以下几点。
- llvm会在使用__weak指针地方,自动补充相关代码。
- 在weak指针首次赋值时,会补充调用
objc_initWeak()
代码。
主要作用是在新对象的弱引用表中加入weak指针。 - 在weak指针被修改值时,会补充调用
objc_storeWeak()
代码。
主要作用是在旧对象的弱引用表中删除weak指针,在新对象的弱引用表中加入weak指针。 - 在weak指针被销毁时,会补充调用
objc_destroyWeak()
代码。
主要作用是在旧对象的弱引用表中删除weak指针。 - 通过2-4的步骤,可以保证每个对象关联SideTable中的weak_table(弱引用表)实时更新。
下面来看另一个问题,当被weak指向的对象销毁时,如何做到让weak指针重新指向nil ?我们可以从对象销毁dealloc()
源码中开始分析。
通过查看obj4源码,dealloc方法依次会进行如下调用。
dealloc
_objc_rootDealloc
obj->rootDealloc
objc_object::rootDealloc
objc_object::rootDealloc
内部实现如下:
#if SUPPORT_NONPOINTER_ISA
inline void objc_object::rootDealloc()
{
//如果是taggedPointer ,直接返回
if (isTaggedPointer()) return; // fixme necessary?
//若没有需要需要特殊处理的标志位,则直接调用c语言free函数进行释放。
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
//有需要需要特殊处理的标志位,对标志位处理释放
object_dispose((id)this);
}
}
#else
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}
#endif
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
inline void objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
void objc_object::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);
}
table.unlock();
}
可以看到,object_dispose()
函数中,先调用objc_destructInstance()
函数处理与该对象相关的一些结构释放,然后再调用free()
释放自身。
objc_destructInstance()
函数中,根据isa中的标志位,依次判断并处理C++析构函数object_cxxDestruct()
,关联对象_object_remove_assocations()
,最后在objc_object::clearDeallocating()
中,处理SideTable中的weak指针和引用计数。
table.refcnts.erase()
会将引用计数擦除,weak_clear_no_lock()
会将弱引用该对象的指针重新指向nil,并从弱引用表中移除与该对象相关的弱引用关系。
weak_clear_no_lock()
函数伪代码如下:
- 在弱引用表中找到与销毁对象相关的entry(weak_entry_t类型),entry存储着所有与该对象有关的弱引用指针地址。
- 如果entry没找到,直接返回。
- 判断entry内部存储是以动态数组还是静态数组,如果是动态数组,将entry->referrers赋给referrers,如果是静态数组,将entry->inline_referrers赋给referrers。
- 遍历referrers,将每个弱引用指针重新指向nil。
- 在弱引用表weak_table中移除该entry。
/// Called on object destruction. Sets all remaining weak pointers to nil.
void weak_clear_no_lock(weak_table_t *weak_table, id referent);
/**
* 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_entry_for_referent获取到弱引用该对象的指针列表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;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//遍历弱引用指针列表,每个referrer对应一个弱引用该对象的指针
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
// 关键代码:如果弱引用指针目前指向该对象,将弱引用指针重新指向nil
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();
}
}
}
// 从SideTable中的弱引用表中移除弱引用关系
weak_entry_remove(weak_table, entry);
}