arc中的weak進階

作為一個iOS開發(fā),相信大家對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函數(shù)執(zhí)行完,編譯器會在結尾添加[super dealloc]的調用從而執(zhí)行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執(zhí)行完之后才開始的

如果是dealloc調用之后才開始清空弱引用表,那為什么在dealloc中調用block回調中打印weakP就已經(jīng)是nil了呢,這里就涉及到第一個關鍵點

我們平時在使用weak對象的時候,并不是直接取出對象地址直接使用,而是對weak對象地址進行了處理

調用了objc_loadWeakRetained函數(shù)

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

可以發(fā)現(xiàn)如果對象的引用計數(shù)是0,就會返回nil
到這里我們可以理解為weak的使用是會先判斷對象的引用計數(shù)的,并不是直接拿來對象地址就直接用了
下一步我們就需要關注引用計數(shù),dealloc之間的關系了,引用計數(shù)在什么時候--的,-(void)dealloc又是在什么時候調用的,繼續(xù)看源碼
先看下release,最終調用rootRelease,對函數(shù)簡化如下

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的函數(shù),事實證明確實是,斷點發(fā)現(xiàn)調用后extra_rc變成了 0
一下主要關注if (slowpath(newisa.isDeallocating())) goto deallocate;
因為先執(zhí)行subc,將extra_rc--變成了0,所以newisa.isDeallocating()結果是true,結果是跳轉到deallocate開始執(zhí)行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執(zhí)行的時候extra_rc已經(jīng)變成了0,就導致newisa.isDeallocating()true,返回了nil

總結:

weak屬性的使用并不是直接拿出存儲的地址直接使用的,或者說并不是直接跟弱引用表掛鉤的,而是判斷弱引用對象的引用計數(shù),如果引用計數(shù)為0,即使弱引用表還存在,也會返回nil

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容