arc中的weak进阶

作为一个iOS开发,相信大家对OC ARC下的weak弱引用都有所了解,底层会有SideTable来保存弱引用指针,当对象被释放时,会清空这个弱引用表,在往下看之前,这些内容是要熟悉的.
今天这个问题我先用一个demo引出

main.h
__weak Person *weakP;

int main(int argc, const char * argv[]) {
    {
        Person *p = [[Person alloc] init];
        p.deallocCallBack = ^{
            NSLog(@"%@",weakP);
        };
        weakP = p;
    }
    return 0;
}
Person
@interface Person : NSObject
@property(nonatomic, copy) void(^deallocCallBack)(void);
@end

@implementation Person
-(void)dealloc {
    self.deallocCallBack();
}
@end

请问在deallocCallBack中打印的weakP是什么
结论是nil,你没有听错,是nil
正常情况下理解会打印Person对象,因为weak被清空是在对象的-dealloc函数执行完,编译器会在结尾添加[super dealloc]的调用从而执行NSObject的dealloc方法,进而去清空弱引用表

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj) {
    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    if (fastpath(isa().nonpointer                     &&
                 !isa().weakly_referenced             &&
                 !isa().has_assoc                     &&
                 !isa().has_cxx_dtor                  &&
                 !isa().has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
#endif // ISA_HAS_INLINE_RC
}

id object_dispose(id obj) {
    if (!obj) return nil;

    objc_destructInstance(obj);
    free(obj);

    return 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_associations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

inline void  objc_object::clearDeallocating() {
    if (slowpath(isa().weakly_referenced || isa().has_sidetable_rc) {
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

这一连串的代码,关于weak的问题就是一句话

弱引用表真实的处理是在对象-(void)dealloc执行完之后才开始的

如果是dealloc调用之后才开始清空弱引用表,那为什么在dealloc中调用block回调中打印weakP就已经是nil了呢,这里就涉及到第一个关键点

我们平时在使用weak对象的时候,并不是直接取出对象地址直接使用,而是对weak对象地址进行了处理

调用了objc_loadWeakRetained函数

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
    obj = *location;
    // 如果传进来的就是小对象或者本身就是nil,直接返回
    if (_objc_isTaggedPointerOrNil(obj)) return obj;
    table = &SideTables()[obj];
    result = obj;
    // 获取类对象
    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // 正常情况不会自定义,所以会进来
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
    // ... 省略
    }
    return result;
}

id objc_object::rootRetain(bool tryRetain (true), objc_object::RRVariant variant(Fast))
{
    if (slowpath(isTaggedPointer())) return (id)this;
    isa_t oldisa;
    isa_t newisa;
    oldisa = LoadExclusive(&isa().bits);

    do {
        //---------------------------------
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa().bits);
            if (sideTableLocked) {
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        //---------------------------------
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa().bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}

重点关注虚线标注的代码

bool isDeallocating() const {
        return extra_rc == 0 && has_sidetable_rc == 0;
}

可以发现如果对象的引用计数是0,就会返回nil
到这里我们可以理解为weak的使用是会先判断对象的引用计数的,并不是直接拿来对象地址就直接用了
下一步我们就需要关注引用计数,dealloc之间的关系了,引用计数在什么时候--的,-(void)dealloc又是在什么时候调用的,继续看源码
先看下release,最终调用rootRelease,对函数简化如下

bool objc_object::rootRelease(bool performDealloc(true), objc_object::RRVariant variant(FastOrMsgSend))
{
    if (slowpath(isTaggedPointer())) return false;

    bool sideTableLocked = false;

    isa_t newisa, oldisa;

    oldisa = LoadExclusive(&isa().bits);

    do {
        newisa = oldisa;
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
    } while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;

    if (variant == RRVariant::Full) {
        if (slowpath(sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!sideTableLocked);
    }
    return false;

deallocate:
    if (performDealloc) {
        this->performDealloc();
    }
    return true;
}

这里重点关注newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);可以看到备注// extra_rc--,说明这个是真正操作extra_rc的函数,事实证明确实是,断点发现调用后extra_rc变成了 0
一下主要关注if (slowpath(newisa.isDeallocating())) goto deallocate;
因为先执行subc,将extra_rc--变成了0,所以newisa.isDeallocating()结果是true,结果是跳转到deallocate开始执行performDealloc

void objc_object::performDealloc() {
    ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}

呀,这不是-(void) dealloc
总结:作用域结束person 调用release,进行了extra_rc - 1变成了0,然后调用-(void) dealloc,所以结合上面的block调用,在block执行的时候extra_rc已经变成了0,就导致newisa.isDeallocating()true,返回了nil

总结:

weak属性的使用并不是直接拿出存储的地址直接使用的,或者说并不是直接跟弱引用表挂钩的,而是判断弱引用对象的引用计数,如果引用计数为0,即使弱引用表还存在,也会返回nil

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容