美文网首页
arc中的weak进阶

arc中的weak进阶

作者: 晨曦中的花豹 | 来源:发表于2024-03-26 17:35 被阅读0次

    作为一个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

    相关文章

      网友评论

          本文标题:arc中的weak进阶

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