探祕Block(三):weak和strong

原做於:2018-01-08
GitHub Repo:BoyangBloghtml

在不特殊說明是MRC的狀況下,默認是ARC。 Objective-C Automatic Reference Counting (ARC)c++

咱們知道,在ARC中,除了全局block,block都是在棧上進行建立的。使用的時候,會自動將它複製到堆中。中間會經歷objc_retainBlock -> _Block_copy -> _Block_copy_internal方法鏈。換過來講,咱們使用的每一個攔截了自動變量的block,都會經歷這寫方法(注意這一點很重要)。git

經過以前的研究,瞭解到在 __main_block_impl_0中會保存着引用到的變量。在轉換過的block代碼中,block會強行持有攔截的外部對象,無論有沒有改變過,都是會形成強引用。github

爲了作好準備,咱們先看一下**__strong__weak**的實現過程。macos

__strong和__weak

__strong

__strong其實是一個默認的方法。數組

{
    id __strong obj = [[NSObject alloc] init];
}
複製代碼

代碼會被轉換成這個樣子markdown

id __attribute__((objc_ownership(strong))) obj = 
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), 
sel_registerName("alloc")), 
sel_registerName("init"));
//代碼實際上只有一行,爲了方便觀看打了換行
複製代碼

抽離出來,實際上主要是這三個方法app

id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);
複製代碼

也就是說,ARC下的對象,正常狀況下都是__strong修飾的。ide

__weak

這裏咱們要使用 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m方法去轉換爲C++代碼,緣由是由於,__weak其實只在ARC的狀態下才能使用,以前使用 clang -rewrite-objc main.m是直接將代碼轉換爲C++,並不有限制。函數

聲明一個__weak對象

{
    id __weak obj = strongObj;
}
複製代碼

轉換以後

id __attribute__((objc_ownership(none))) obj1 = strongObj;
複製代碼

相應的會調用

id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);
複製代碼

從名字上能夠看出來,一個是建立一個是銷燬。

這裏LLVM文檔和objc_723文檔有些許不一樣。我這裏採用最新的objc_723代碼,比以前的有優化:

id objc_initWeak(id *location, id newObj) {
    // 查看對象實例是否有效
    // 無效對象直接致使指針釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    
    // 這裏傳遞了三個 bool 數值
    // 使用 template 進行常量參數傳遞是爲了優化性能
    // DontHaveOld--沒有舊對象,
    // DoHaveNew--有新對象,
    // DoCrashIfDeallocating-- 若是newObj已經被釋放了就Crash提示
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
~~~~~~~~~~~~~~~~
void objc_destroyWeak(id *location) {
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
複製代碼

這兩個方法,最後都指向了 storeWeak方法,這是一個很長的方法:

// Update a weak variable.
// If HaveOld is true, the variable has an existing value 
// that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be 
// assigned into the variable. This value might be nil.
// 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.
// 更新weak變量.
// 當設置HaveOld是true,即DoHaveOld,表示這個weak變量已經有值,須要被清理,這個值也有能是nil
// 當設置HaveNew是true, 即DoHaveNew,表示有一個新值被賦值給weak變量,這個值也有能是nil
//當設置參數CrashIfDeallocating是true,即DoCrashIfDeallocating,若是newObj已經被釋放或者newObj是一個不支持弱引用的類,則暫停進程
// deallocating或newObj的類不支持弱引用
// 當設置參數CrashIfDeallocating是false,即DontCrashIfDeallocating,則存儲nil

enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    assert(haveOld  ||  haveNew);
    // 初始化當前正在 +initialize 的類對象爲nil
    if (!haveNew) assert(newObj == nil);
    Class previouslyInitializedClass = nil;
    id oldObj;
    
    // 聲明新舊SideTable,
    SideTable *oldTable;
    SideTable *newTable;

    // 得到新值和舊值的鎖存位置(用地址做爲惟一標示)
    // 經過地址來創建索引標誌,防止桶重複
    // 下面指向的操做會改變舊值
 retry:
    
    // 若是weak ptr以前弱引用過一個obj,則將這個obj所對應的SideTable取出,賦值給oldTable
    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.
    //經過確保沒有弱引用的對象具備未初始化的 isa,防止弱引用機制和 +initialize 機制之間的死鎖。
    //在使用 +initialized 方法的時候,由於這個方法是在alloc以前調用的。不這麼作,可能會出現+initialize 中調用了 storeWeak 方法,而在 storeWeak 方法中 weak_register_no_lock 方法中用到對象的 isa 尚未初始化完成的狀況。

    if (haveNew  &&  newObj) {
        // 得到新對象的 isa 指針
        Class cls = newObj->getIsa();
        // 判斷 isa 非空且已經初始化
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 解鎖新舊SideTable
            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.
            // 若是 newObj 已經完成執行完 +initialize 是最理想狀況
            // 若是 newObj的 +initialize 仍然在線程中執行
            // (也就是說newObj的 +initialize 正在調用 storeWeak 方法)
            // 經過設置previousInitializedClass以在重試時識別它。
            
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    // 清除舊值,其實是清除舊對象weak_table中的location

    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    // 分配新值,其實是保存location到新對象的weak_table種

    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.
        // 若是弱引用被釋放 weak_register_no_lock 方法返回 nil
        
        // 若是新對象存在,而且沒有使用TaggedPointer技術,在引用計數表中設置若引用標記位
        if (newObj  &&  !newObj->isTaggedPointer()) {
            // 標記新對象有weak引用,isa.weakly_referenced = true;
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // 設置location指針指向newObj
        // 不要在其餘地方設置 *location。 那會引發競爭
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
複製代碼

這裏再也不重複一遍weak的實現,有興趣的能夠去@property的研究(二)查看。

簡單點說,因爲weak也是用哈希表實現的,因此objc_storeWeak函數就把第一個入參的變量地址註冊到weak表中,而後根據第二個入參來決定是否移除。若是第二個參數爲0,那麼就把**__weak**變量從weak表中刪除記錄,並從引用計數表中刪除對應的鍵值記錄。

因此若是**__weak引用的原對象若是被釋放了,那麼對應的__weak**對象就會被指爲nil。這部分就是經過objc_storeWeak函數這些函數來實現的。

weakSelf和strongSelf

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;      
複製代碼

weakSelf是爲了讓block不去持有self,避免了循環引用,若是在Block內須要訪問使用self的方法、變量,建議使用weakSelf。

可是,這裏會出現一個問題。使用weakSelf修飾的 **self.**變量,是有可能在執行的過程當中就被釋放的。

如下代碼爲例

- (void)blockRetainCycle_1 {
    __weak __typeof(self)weakSelf = self;
    self.block = ^{
        NSLog(@"%@",@[weakSelf]);
    };
}
複製代碼

咱們若是直接使用這個函數,是有可能在打印以前,weakSelf就被釋放了,打印出來就是會出問題。 爲了解決這個問題,咱們就要用到strongSelf。

- (void)blockRetainCycle_2 {
    __weak __typeof(self)weakSelf = self;
    self.block = ^{
        __strong typeof (weakSelf)strongSelf = weakSelf;
        NSLog(@"%@",@[strongSelf]);
    };
}
複製代碼

在這裏,咱們使用了strongSelf,它能夠保證在strongSelf下面,直到出了做用域以前,都是存在這個strongSelf的。

可是,這裏依然存在一個微小的問題:
咱們知道使用weakSelf的時候是沒法保證在做用域中一直持有的。雖然使用了strongSelf,可是仍是會存在微小的機率,讓weakSelf在strongSelf建立以前被釋放。若是是單純的給self對象發送信息的話,這麼其實問題不大,OC的消息轉發機制保證了咱們即便給nil的對象發送消息也不會出現問題

可是若是咱們有其餘的操做,好比說將self對象添加進數組中,如上面代碼所示,這裏就會發生crash了。

那麼咱們要須要進一步的保護

- (void)blockRetainCycle_3 {
    __weak __typeof(self)weakSelf = self;
    self.block = ^{
        __strong typeof (weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"%@",@[strongSelf]);
        }
    };
}
複製代碼

block中__weak和__block的區別

咱們使用__block其實也是可能達到防止block循環引用的。

咱們能夠經過在block內部把__block修飾的對象置爲nil來變相地實現內存釋放。

從內存上來說,__block會持有該對象,即便超出了該對象的做用域,該對象仍是會存在的,直到block對象從堆上銷燬;而__weak是把該對象賦值給weak對象,若是對象被銷燬,weak對象將變成nil。

另外,__block對象可讓block修改局部變量,__weak則不能夠。

關鍵字

咱們經過以前的文章知道,在ARC當中,通常的block會從棧被copy到堆中。

可是若是使用weak呢?(assign就不討論了)

系統會告知咱們 Assigning block literal to a weak property; object will be released after assignment

而在ARC下要使用什麼關鍵字呢?strong和copy都是能夠的。 經過以前的文章能夠知道,在ARC中,block會自動從棧被複制到堆中,這個copy是系統自動進行了,即便使用strong仍是依然會有copy操做。因此說,若是爲了嚴謹些,使用copy是能夠的,可是使用strong也無傷大雅。

總結

  • 若是block內部使用到了某個變量,並且這個變量是局部變量,那麼block會捕獲這個變量並存儲到block底層的結構體中。
  • 若是捕獲的這個變量是用__weak修飾的,那麼block內部就是用弱指針指向這個變量(也就是block不持有這個對象),反之使用__strong,那麼block內部就是用強指針指向這個對象(也就是block持有這個對象)。
  • self在某種意義上也是一個局部變量。
  • 若是self並不持有這個block,block內部怎麼引用self都不會形成循環引用。

下一篇文章block(四):修改block的實現

相關文章
相關標籤/搜索