在討論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ù)制到堆空間
- Block變量使用copy
- Block變量被__strong修飾符修飾
- 方法返回Block變量
- 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ì)象,賦值的瞬間就被釋放了。