weak深入分析

__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()函数主要依次做了以下事情:

  1. 如果有旧对象oldObj,从全局SideTables获取到旧对象关联的SideTable的指针,赋值给oldTable。
    相关代码:oldTable = &SideTables()[oldObj];
  2. 如果有新对象newObj,在全局SideTables创建新对象关联的SideTable,并赋值给newTable。
    相关代码:newTable = &SideTables()[newObj];
  3. 如果有旧对象oldObj。从oldTable的weak_table(弱引用表)清除与弱指针的地址location的关联。
    相关代码:weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
  4. 如果有新对象newObj。在newTable的weak_table(弱引用表)增加弱指针的地址location。
    相关代码:newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
  5. 如果有新对象newObj。在newTable的refcnts(引用计数)中有一位是标记是否存在弱引用关系,将其标记为1。
    相关代码:newObj->setWeaklyReferenced_nolock();
  6. 如果有新对象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);
}

对上面的分析进行归纳,主要是以下几点。

  1. llvm会在使用__weak指针地方,自动补充相关代码。
  2. 在weak指针首次赋值时,会补充调用objc_initWeak()代码。
    主要作用是在新对象的弱引用表中加入weak指针。
  3. 在weak指针被修改值时,会补充调用objc_storeWeak()代码。
    主要作用是在旧对象的弱引用表中删除weak指针,在新对象的弱引用表中加入weak指针。
  4. 在weak指针被销毁时,会补充调用objc_destroyWeak()代码。
    主要作用是在旧对象的弱引用表中删除weak指针。
  5. 通过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()函数伪代码如下:

  1. 在弱引用表中找到与销毁对象相关的entry(weak_entry_t类型),entry存储着所有与该对象有关的弱引用指针地址。
  2. 如果entry没找到,直接返回。
  3. 判断entry内部存储是以动态数组还是静态数组,如果是动态数组,将entry->referrers赋给referrers,如果是静态数组,将entry->inline_referrers赋给referrers。
  4. 遍历referrers,将每个弱引用指针重新指向nil。
  5. 在弱引用表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);
}

你可能感兴趣的:(weak深入分析)