OC底層-Block本質(5、捕獲的變量什麼時候銷燬)

疑問

block通常使用過程當中都是對對象變量的捕獲,那麼對象變量的捕獲同基本數據類型變量相同嗎? 當在block中訪問的爲對象類型時,對象何時會銷燬?ios

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"------block內部%d",person.age);
            };
            // 執行完畢,person沒有被釋放
        }
        NSLog(@"--------");
        // Person --- dealloc
    }
    return 0;
}
複製代碼

大括號執行完畢以後,person不會被釋放。,person爲auto變量,傳入的block的變量一樣爲person,即block有一個強引用引用person,因此block不被銷燬的話,person也不會銷燬。c++

查看源代碼確實如此bash

將上述代碼轉移到MRC環境下,在MRC環境下即便block還在,person卻被釋放掉了。由於MRC環境下block在棧上,棧上的block對外面的person不會進行強引用。iphone

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"------block內部%d",person.age);
            };
            [person release];
            // Person --- dealloc
        }
        NSLog(@"--------");
        
    }
    return 0;
}

block調用copy操做以後,person不會被釋放。

block = [^{
   NSLog(@"------block內部%d",person.age);
} copy];


複製代碼

只須要對棧空間的block進行一次copy操做,將棧空間的block會拷貝到堆中,person就不會被釋放,說明堆空間的block可能會對person進行一次retain操做,以保證person不會被銷燬。堆空間的block本身銷燬以後也會對持有的對象進行release操做。函數

也就是說棧空間上的block不會對對象強引用,堆空間的block有能力持有外部調用的對象,即對對象進行強引用或去除強引用的操做。ui

__weak

__weak添加以後,person在做用域執行完畢以後就被銷燬了。spa

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *waekPerson = person;
            block = ^{
                NSLog(@"------block內部%d",waekPerson.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

複製代碼

將代碼轉化爲c++來看一下上述代碼之間的差異。 __weak修飾變量,須要告知編譯器使用ARC環境及版本號不然會報錯,添加說明-fobjc-arc -fobjc-runtime=ios-8.0.03d

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m指針

__weak修飾的變量,在生成的__main_block_impl_0中也是使用__weak修飾

__main_block_copy_0 和 __main_block_dispose_0

當block中捕獲對象類型的變量時,咱們發現block結構體__main_block_impl_0的描述結構體__main_block_desc_0中多了兩個參數copy和dispose函數,查看源碼:code

copy和dispose函數中傳入的都是__main_block_impl_0結構體自己。

copy本質就是__main_block_copy_0函數,__main_block_copy_0函數內部調用_Block_object_assign函數,_Block_object_assign中傳入的是person對象的地址,person對象,以及8。

dispose本質就是__main_block_dispose_0函數,__main_block_dispose_0函數內部調用_Block_object_dispose函數,_Block_object_dispose函數傳入的參數是person對象,以及8。

_Block_object_assign函數調用時機及做用

當block進行copy操做的時候就會自動調用__main_block_desc_0內部的__main_block_copy_0函數,__main_block_copy_0函數內部會調用_Block_object_assign函數。

_Block_object_assign函數會自動根據__main_block_impl_0結構體內部的person是什麼類型的指針,對person對象產生強引用或者弱引用。能夠理解爲_Block_object_assign函數內部會對person進行引用計數器的操做,若是__main_block_impl_0結構體內person指針是__strong類型,則爲強引用,引用計數+1,若是__main_block_impl_0結構體內person指針是__weak類型,則爲弱引用,引用計數不變。 _Block_object_dispose函數調用時機及做用

當block從堆中移除時就會自動調用__main_block_desc_0中的__main_block_dispose_0函數,__main_block_dispose_0函數內部會調用_Block_object_dispose函數。

_Block_object_dispose會對person對象作釋放操做,相似於release,也就是斷開對person對象的引用,而person到底是否被釋放仍是取決於person對象本身的引用計數。

總結

一旦block中捕獲的變量爲對象類型,block結構體中的__main_block_desc_0會出兩個參數copy和dispose。由於訪問的是個對象,block但願擁有這個對象,就須要對對象進行引用,也就是進行內存管理的操做。好比說對對象進行retarn操做,所以一旦block捕獲的變量是對象類型就會會自動生成copy和dispose來對內部引用的對象進行內存管理。

當block內部訪問了對象類型的auto變量時,若是block是在棧上,block內部不會對person產生強引用。不論block結構體內部的變量是__strong修飾仍是__weak修飾,都不會對變量產生強引用。

若是block被拷貝到堆上。copy函數會調用_Block_object_assign函數,根據auto變量的修飾符(__strong,__weak,unsafe_unretained)作出相應的操做,造成強引用或者弱引用

若是block從堆中移除,dispose函數會調用_Block_object_dispose函數,自動釋放引用的auto變量。
複製代碼

疑問

person什麼時候銷燬

一、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",person);
    });
    NSLog(@"touchBegin----------End");
}

複製代碼

二、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",waekP);
    });
    NSLog(@"touchBegin----------End");
}

複製代碼

三、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"weakP ----- %@",waekP);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"person ----- %@",person);
        });
    });
    NSLog(@"touchBegin----------End");
}

複製代碼

四、

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"person ----- %@",person);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"weakP ----- %@",waekP);
        });
    });
    NSLog(@"touchBegin----------End");
}

複製代碼

總結疑問

一、block做爲GCD API的方法參數時會自動進行copy操做,所以block在堆空間,而且使用強引用訪問person對象,所以block內部copy函數會對person進行強引用。當block執行完畢須要被銷燬時,調用dispose函數釋放對person對象的引用,person沒有強指針指向時纔會被銷燬。

故:在3秒後銷燬person對象

二、block中對waekP爲__weak弱引用,所以block內部copy函數會對person一樣進行弱引用,當大括號執行完畢時,person對象沒有強指針引用就會被釋放。所以block塊執行的時候打印null。

故:立馬銷燬person對象

第三個和第四個你們可自行驗證。

相關文章
相關標籤/搜索