Block截獲對(duì)象的內(nèi)存思考

在討論Block截獲對(duì)象的內(nèi)存變化前。先看一下Block截獲對(duì)象時(shí),截獲的是什么。

下面舉個(gè)例子

// main.m
#import <Foundation/Foundation.h>
typedef void(^blk)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [[NSMutableArray alloc] init];
        blk block = ^{
          NSLog(@"%@", array)   // 內(nèi)部截獲了array對(duì)象
        };
        block();
    }
    return 0;
}

這樣看不夠直觀,通過clang將Objective-C代碼轉(zhuǎn)換為C++源碼。

步驟如下:

1.找到對(duì)應(yīng)的文件所在

2.在終端中輸入

clang -rewrite-objc main.m

3.得到main.cpp

找到關(guān)鍵點(diǎn)__main_block_impl_0。就是對(duì)應(yīng)上述blk類型變量的對(duì)應(yīng)結(jié)構(gòu)體

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;        // 成員變量,用來保存外部傳入的array指針值
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以明顯得看到結(jié)構(gòu)體內(nèi)部含有成員變量,NSMutableArray *array,就是用來存儲(chǔ)外部傳入的array指針值。

由此可證最開始的猜想:Block截獲對(duì)象時(shí),保存的指向?qū)ο蟮闹羔樦怠?/p>

Block截獲對(duì)象時(shí)的,對(duì)象的內(nèi)存變量

確切來說,是對(duì)象引用計(jì)數(shù)的變化。

接著針對(duì)上面的例子,獲得截獲對(duì)象的引用計(jì)數(shù)。

// main.m
#import <Foundation/Foundation.h>
typedef void(^blk)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [[NSMutableArray alloc] init];
        // 位置1
        NSLog(@"count:%ld", CFGetRetainCount((__bridge CFMutableArrayRef)array));           
        // 位置2
        blk block = ^{
            NSLog(@"%@", array);
        };      
        // 位置3  
        NSLog(@"count:%ld", CFGetRetainCount((__bridge CFMutableArrayRef)array));           
    }
    return 0;
}

下面來看看輸出結(jié)果

count:1
count:3

位置1時(shí),count為1不難理解,此時(shí)指向?qū)ο蟮闹挥幸粋€(gè)array指針。而過了位置2之后,count變成了3。

這里有兩個(gè)疑惑點(diǎn)。

1.block沒有執(zhí)行的情況下,對(duì)象已經(jīng)被截獲了?

2.count為什么是3,而不是2?

一個(gè)一個(gè)來解答。

1.block沒有執(zhí)行的情況下,對(duì)象已經(jīng)被截獲了?

通過clang的源碼轉(zhuǎn)換如下。

// main.cpp
// 簡化結(jié)果
int main(int argc, const char * argv[]) {
    blk block = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344);
    return 0;
}

可以看到將__main_block_impl_0賦值給了block變量,而__main_block_impl_0正是之前例子提到的Block變量轉(zhuǎn)化后的結(jié)構(gòu)體。結(jié)構(gòu)體內(nèi)部有一個(gè)成員變量專門用來存對(duì)象指針的值。因此只要定了Block,對(duì)象就已經(jīng)被截獲了。

2.count為什么是3,而不是2?

通過第一個(gè)解答,得知此時(shí)array對(duì)象已經(jīng)被截獲。那么引用計(jì)數(shù)+1。但為什么多了1。

原因是,在ARC的情況下,有以下4中情況,會(huì)將Block從??臻g復(fù)制到堆空間

  1. Block變量使用copy
  2. Block變量被__strong修飾符修飾
  3. 方法返回Block變量
  4. Cococa框架中帶“usingBlock”的方法及GCD中的API

在ARC中,指向OC對(duì)象的變量沒有明確寫所有權(quán)修飾符,則修飾符默認(rèn)為__strong。(實(shí)際上Block就是一種OC對(duì)象)

blk block = ^{
            NSLog(@"%@", array);
        };

等價(jià)于

blk __strong block = ^{
            NSLog(@"%@", array);
        };

根據(jù)規(guī)則2,“Block變量被__strong修飾符修飾”時(shí),會(huì)將Block從棧空間復(fù)制到堆空間。

則此時(shí)內(nèi)存中有兩份Block,且兩份Block又各自截獲了array對(duì)象。因此此時(shí)指向?qū)ο蟮囊糜?jì)數(shù)為3(array變量+棧Block截獲+堆Block截獲)。

那么,如何使count變?yōu)?呢?

思路就是只保留??臻g的Block。

// main.m
#import <Foundation/Foundation.h>
typedef void(^blk)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [[NSMutableArray alloc] init];
        NSLog(@"count:%ld", CFGetRetainCount((__bridge CFMutableArrayRef)array));           
        
        blk __unsafe_unretained block = ^{
            NSLog(@"%@", array);
        };          // 使用__unsafe_unretained修飾
        NSLog(@"count:%ld", CFGetRetainCount((__bridge CFMutableArrayRef)array));           
    }
    return 0;
}

結(jié)果

count:1
count:2

通過__unsafe_unretained進(jìn)行修飾,即可只保留??臻g的Block。__unsafe_unretained作用于weak相似,但是不會(huì)自動(dòng)置為nil。

備注:

不用weak的原因是因?yàn)锽lock剛生成對(duì)象,賦值的瞬間就被釋放了。

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容