__weak详解

object默认的修饰符是__strong,然而在开发中我们也经常使用__weak,用它来解决循环引用的问题:两个对象相互引用无法释放,造成内存泄露。用__weak来破坏一个强引用,来达到正常释放的目的。这种情况常见于block中,但是有没有想过为什么block会强引用block中的对象呢?答案也很简单,我们手动或者编译器帮我们对block进行了copy的操作,所以block会对其块中的对象就行强引用
!这个是一个小知识点,有的面试中可能会问到。
上面的都不是本篇文章的重点,重点是用__weak修饰的变量有两个特点:1.__weak修饰的对象释放后会自动致为nil;2.__weak修饰的对象注册到autoreleasepool中。今天我们来重点分析下__weak的这两个特点。
通过苹果开源代码,找到了与__weak相关变量函数:

第一点:__weak修饰的对象释放后会自动致为ni
//创建
id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

//销毁
void objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

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) {
        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修饰的变量由weak表统一管理,这个和引用计数表一样,都是作为散列表被实现的。
例如:

__weak id weakObj = obj; 
转换=>
objc_initWeak(&weakObj, obj);
objc_destroyWeak(&weakObj);

objc_initWeak函数把obj地址作为键值,把weakObj的地址注册到weak表中。当销毁的时候objc_destroyWeak调用storeWeak传入nil.对于一个键值可以注册多个变量的地址,以obj地址作为键值的weak变量可能是多个,就会以ojb为键值进行检索,并做了以下操作:
(1)从weak表中获取废弃对象的地址为键值的记录
(2)将记录中的__weak修饰符修饰的变量的地址赋值为nil
(3)从weak表中删除该记录
(4)从引用计数表中删除废弃对象的地址为键值的记录
所以从上面的过程来看,__weak修饰的对象废弃之后,就会至为nil。由此也能看出来,这些步骤的执行必定会消耗一定的CPU资源,最好的办法是在需要使用__weak的时候才去使用!!!

第二点:__weak修饰的对象注册到autoreleasepool中
__weak id weakObj = obj; 
NSLog(@"%@", weakObj);
转换=>
objc_initWeak(&weakObj, obj);
let tmp = objc_loadWeakRetained(&weakObj);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&weakObj);

当有__weak修饰符的情况下多了objc_loadWeakRetained函数和objc_autorelease函数的调用。
objc_loadWeakRetained:取出__weak变量并retain
objc_autorelease:将对象注册到autoreleasepool中,因为对象为弱引用在访问的过程中随时都有可能释放,如果那它加入到autoreleasepool中,那么在autoreleasepool未释放之前对象可以放心使用了。

另外建议在使用__weak的时候同时使用__strong来强引用weak指针。如下:

__weak typeof(self) weakSlef = self;
self.block = ^{
    __strong typeof(weakSlef) strongSelf = weakSelf;

};

这样做的目的是为了在block执行的过程中self被释放,导致一些异常问题甚至是crash。

奇巧淫技

另外介绍一种解决block循环引用的方法:

__weak typeof(self) weakSlef = self;
self.block = ^{

    self.block = nil; 
    或
    self = nil;

};

任何一个至为nil都可以解除循环引用,但是有个缺点:必须执行block才能解除循环引用,可在一些会造成循环引用的但又不能用__weak的时候使用。

参考:
《Objective-C高级编程》

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值