Block學習⑤--block對物件變數的捕獲

潘小懶同學發表於2018-07-10

在前面我們已經講過對基本資料型別的變數捕獲Block學習②--block的變數捕獲,那麼對物件變數的捕獲是否和對基本資料型別一樣呢,我們來看下面程式碼

#import <Foundation/Foundation.h>
#import "RGPerson.h"

typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        {
            RGPerson *person = [[RGPerson alloc] init];
            person.name = @"xxx";
            
            block = ^{
                NSLog(@"%@",person.name);
            };
        }
        NSLog(@"________");
        block ();
        
    }
    return 0;
}

// 列印結果:
2018-07-10 15:41:33.056744+0800 block-01[1813:115684] ________
2018-07-10 15:41:33.057089+0800 block-01[1813:115684] xxx
2018-07-10 15:41:33.057115+0800 block-01[1813:115684] -[RGPerson dealloc]
複製程式碼

person在作用域結束之後,person物件並沒有被釋放,person物件作為auto物件,會被block捕獲,但為什麼沒有被釋放呢,我們的猜想應該就是被block強引用了,我們通過C++原始碼來檢視一下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  RGPerson *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, RGPerson *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製程式碼

上述程式碼中,可以看到,我們的猜想確實如此,這只是在ARC的環境下,那我們要是切換到MRC環境下呢,畢竟ARC環境下,系統會幫我們做很多事情

#import <Foundation/Foundation.h>
#import "RGPerson.h"

typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        {
            RGPerson *person = [[RGPerson alloc] init];
            person.name = @"xxx";
            
            block = ^{
                NSLog(@"%@",person.name);
            };
            [person release];
        }
        NSLog(@"________");
        block ();
        
    }
    return 0;
}
//列印結果:
2018-07-10 15:51:28.042472+0800 block-01[1890:121165] -[RGPerson dealloc]
2018-07-10 15:51:28.042792+0800 block-01[1890:121165] ________
2018-07-10 15:51:28.042870+0800 block-01[1890:121165] xxx
複製程式碼

通過檢視列印結果,我們可以知道,在MRC環境下,person在作用域結束後,就被釋放了,我們對block進行一次copy呼叫,person沒有被釋放

前面的文字我們也提到過,對棧上面的block進行一次copy呼叫,會將block複製到堆上面,既然到了堆上面,那麼就相當於對block進行了一次retain操作,只要block不被銷燬,person物件也不會銷燬,也就是說,在棧上面,block不會對auto變數進行強引用

__weak

在ARC環境下,為程式碼加上一個__weak修飾符,再進行列印

#import <Foundation/Foundation.h>
#import "RGPerson.h"

typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        {
            RGPerson *person = [[RGPerson alloc] init];
            person.name = @"xxx";
            
            __weak RGPerson *weakP = person;
            block = ^{
                NSLog(@"%@",weakP.name);
            };
        }
        NSLog(@"________");
        block ();
        
    }
    return 0;
}
//列印結果
2018-07-10 16:10:08.824923+0800 block-01[1972:130602] -[RGPerson dealloc]
2018-07-10 16:10:08.825252+0800 block-01[1972:130602] ________
2018-07-10 16:10:08.825314+0800 block-01[1972:130602] (null)
複製程式碼

person物件在作用域結束後就被釋放了,為了能夠更加清晰的看到block內部的實現,我們將其轉換成C++,但是我們要使用另外的一個命令列,防止因為__weak而導致報錯

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製程式碼

轉換成功以後如下

Block學習⑤--block對物件變數的捕獲

然後我們把__weak修飾符去掉在進行轉換

Block學習⑤--block對物件變數的捕獲

我們可以看到,兩張圖中person使用不同的修飾符,而這兩個修飾符有起了什麼作用呢,我們繼續往下看

Block學習⑤--block對物件變數的捕獲

在瞭解block本質的時候,我們知道,在block的Desc當中,只有兩個引數,而現在卻又多出了兩個函式,這兩個函式是幹什麼的呢?

__main_block_copy_0

Block學習⑤--block對物件變數的捕獲

在函式 __main_block_desc_0的copy其實就是 __main_block_copy_0函式

__main_block_dispose_0

Block學習⑤--block對物件變數的捕獲

在函式 __main_block_desc_0的dispose其實就是 __main_block_dispose_0函式

上面的copy和dispose相當於OC程式碼中的retain和release,在上面的圖中我們可以發現,兩個函式都呼叫了_Block_object_assign這個函式,這個函式是有什麼用的呢?我們繼續往下看

_Block_object_assign

呼叫 _Block_object_assign函式,我們需要將person物件傳入,以及其他的引數。

__main_block_copy_0對_Block_object_assign呼叫

當對該函式進行呼叫時,該函式會根據__main_block_impl_0結構體內部,person物件的修飾符來進行處理,當使用的是__strong時,將會對person物件的引用計數加1,當為__weak時,引用計數不變

__main_block_dispose_0對_Block_object_assign呼叫

當對該函式進行呼叫,會對person物件進行釋放操作

總結

當block內部訪問了物件型別的auto變數時

如果block是在棧上面,將不會對auto變數產生強引用

如果block被拷貝到堆上,會呼叫block內部的copy函式,copy函式內部會呼叫_Block_object_assign函式,_Block_object_assign函式會根據auto變數的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用

如果block從堆上移除,會呼叫block內部的dispose函式,dispose函式內部會呼叫_Block_object_dispose函式,_Block_object_dispose函式會自動釋放引用的auto變數(release)

相關文章