美文网首页IOS 拾遗程序员iOS进阶之路
(二)并不很深入地探究 arc中 __weak 修饰符

(二)并不很深入地探究 arc中 __weak 修饰符

作者: madaoCN | 来源:发表于2017-10-02 23:29 被阅读26次

关联文章

(一)并不很深入地探究 arc中 __strong 修饰符
(二)并不很深入地探究 arc中 __weak 修饰符
(三)并不很深入地探究 arc中 __autoreleassing 修饰符

ARC是由编译器和运行时系统进行内存管理,本文将从编译器和runtime系统出发,探究 __weak 修饰符的实现过程。

在此之前我们需要知道如何获取汇编输出

iOS 获取汇编输出方法

对于__weak 修饰符:

  • 若附有__weak 修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
  • 若使用__weak 修饰符的变量,是否是使用注册到autoreleasepool中的对象?

接下来针对以上两点进行整理 (便于理解,下文汇编输出将整理成伪代码)

若附有__weak 修饰符的变量所引用的对象被废弃,则将nil赋值给该变量

id obj = [[NSObject alloc] init];
id __weak obj1 = obj;

汇编输出伪代码

id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

通过objc_initWeak初始化带有__weak修饰符的变量,作用域结束时通过objc_destroyWeak释放。

objc_initWeakobjc_destroyWeak源码:

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj); //使用到了storeWeak
}

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);//使用到了storeWeak
}

可见创建weak变量和销毁变量都用到了storeWeak方法

storeWeak是段函数模板

/* storeWeak 部分代码 Part 1 */
// 更新weak变量
// 如果 HaveOld 为 true,变量已经存在值,因此需要进行清理操作,变量值可以为空
// 如果 HaveNew 为 true,将有个新值被分配到变量中,这个值可以为空
// 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.
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;

然后SideTable为何物?

struct SideTable {
    spinlock_t slock; 
    RefcountMap refcnts;
    weak_table_t weak_table;
}

slock 是一个自旋锁,用来对SideTable的操作加锁,refcnts从变量类型看,应该是存储引用计数的散列表, weak_table 则是存放了弱引用。在weak表中,将对象的地址作为键值进行检索,能够高速获取对于__weak修饰符变量的地址。

/* storeWeak 部分代码 Part 2 */
    // 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;
    }

这段主要是获取oldObj , oldTable, newTable,并给oldTable, newTable上锁

/* storeWeak 部分代码 Part 3 */

// 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;
    }
}

这段主要确保newObj+initialize被正确调用

/* storeWeak 部分代码 Part 4 */

// 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;
}

最后一部分代码:

  • 第一步: 如果 haveOld为真,调用weak_unregister_no_lock来删除oldTable中的weak表中记录
  • 第二步: 如果 haveNew为真,调用weak_register_no_locknewTable中的weak表删除对应记录,然后再调用 setWeaklyReferenced_nolock 将引用计数表中做SIDE_TABLE_WEAKLY_REFERENCED标记
  • 最后SideTable释放锁,返回newObj

然后,我们回到之前的objc_initWeakobjc_destroyWeak方法,

  • objc_initWeak,调用 storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj)
    因为DontHaveOld为 false ,DoHaveNew为 True, 所以调用了上面第二步
  • objc_destroyWeak调用storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> (location, nil)
    因为 DoHaveOld 为 true, DontHaveNew为false 所以调用了上面第一步

由上述分析, __weak 通过散列表来实现,如果大量使用会消耗相应的资源(废弃对象时需要weak表中查找删除对应记录,在引用计数表中也要做类似操作),所以只在需要避免循环引用的时候使用__weak修饰符

若使用__weak 修饰符的变量,是否是使用注册到autoreleasepool中的对象?
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"%@", obj1);

汇编输出伪代码

id obj1;
objc_initWeak(&obj1, obj);
id temp = objc_loadWeakRetained(&obj1);
NSLog(@"%@", temp);
objc_release(temp);
objc_destroyWeak(&obj1);

objc_loadWeakRetained部分源码

if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        assert(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, SEL_retainWeakReference);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
                result = nil;
            }
        }
        else {
            table->unlock();
            _class_initialize(cls);
            goto retry;
        }
    }

  • rootTryRetaintryRetain用来进行对变量进行 retain操作, 所以objc_loadWeakRetained函数去除赋有__weak修饰符变量引用的对象,并且retain

然而令人诧异的是,objc_loadWeakRetained值 并没有加入autoreleasepool,那么它是如何保证使用期间时候obj1存在的呢, 令人费解,本人暂时无法给出解答,希望有大牛来给给予指导。

相关文章

网友评论

  • Nipengnini:我想偷偷摸摸地问一下 您做马甲壳吗
    madaoCN:@Nipengnini 现在公司有做过几个马甲包
  • 啊哈呵:使用__weak 修饰符的变量,注册到autoreleasepool中的对象,我是在Objective-C高级编程 iOS与OS X多线程和内存管理看到,这是以前的老版本如下:
    id temp = objc_loadWeakRetained(&obj1);
    objc_autorelease(temp);
    NSLog(@"%@", temp);//先autorelease然后在使用。

    现在的版本,也是文章上面的:
    id temp = objc_loadWeakRetained(&obj1);
    NSLog(@"%@", temp);
    objc_release(temp);// 先使用temp,使用完了,直接release

    我觉得现在版本这个方式比老版本好啊,苹果爹爹内核系统部的大神们,就是在研究这些,怎么改实现怎么改能效率更好。
    很多东西经常改的,有些内部原理会被不断修改,也会经常改变我们的认知,苹果爹爹:joy:
    madaoCN:@啊哈呵 😁与时俱进

本文标题:(二)并不很深入地探究 arc中 __weak 修饰符

本文链接:https://www.haomeiwen.com/subject/ltonextx.html