block 實(shí)際上是OC 對(duì)閉包c(diǎn)losure的實(shí)現(xiàn)
block的數(shù)據(jù)結(jié)構(gòu)
先來(lái)看下block的結(jié)構(gòu)示意圖:
block的數(shù)據(jù)結(jié)構(gòu)
各組成部分含義:
- isa:該對(duì)象是什么
- flags:block附加信息
- reserve:保留變量
- invoke:函數(shù)實(shí)現(xiàn)指針
- descriptor:block 描述符,主要是block的一些附加信息
- variables:capture過(guò)來(lái)的變量
這樣來(lái)看的話,block就是函數(shù) + 數(shù)據(jù),即持有著一些數(shù)據(jù)的函數(shù),編譯后生成相應(yīng)的結(jié)構(gòu)體和函數(shù)指針,結(jié)構(gòu)體保存著數(shù)據(jù),
block的三種類型
在OC中block的類型分為三類:
- _NSConcreteGlobalBlock 全局的靜態(tài) block,不會(huì)訪問(wèn)任何外部變量。
- _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷毀。
- _NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷毀。
以下說(shuō)明幾點(diǎn)需要注意的:
- _NSConcreteGlobalBlock 是全局靜態(tài)block,結(jié)構(gòu)體存儲(chǔ)在數(shù)據(jù)區(qū)。
- 常見(jiàn)的是有捕獲外部變量的_NSConcreteStackBlock,需要注意的是如果這種類型的block 定義在函數(shù)內(nèi)部,當(dāng)函數(shù)執(zhí)行完畢,退棧的時(shí)候會(huì)將該block結(jié)構(gòu)體所占的內(nèi)存空間釋放掉,這樣再引用的話會(huì)報(bào)錯(cuò)。
- _NSConcreteMallocBlock 通常不會(huì)在源碼中直接出現(xiàn),OC ARC下會(huì)對(duì)_NSConcreteStackBlock 進(jìn)行優(yōu)化,將其copy到堆上,轉(zhuǎn)換成_NSConcreteMallocBlock,所以無(wú)特殊處理,OC中將只會(huì)有1,3兩種類型block
- _NSConcreteStackBlock捕獲的局部變量,如不加_block修飾符,將會(huì)把變量copy一份到其結(jié)構(gòu)體中,所以才會(huì)在內(nèi)部修改不影響外部變量,加_block修飾之后,結(jié)構(gòu)體中會(huì)添加一個(gè)__Block_byref_i_0 的結(jié)構(gòu)體,且復(fù)制的是變量地址,達(dá)到可以修改外部變量的效果。關(guān)于這部分詳細(xì):這里
block的內(nèi)存測(cè)試
下面簡(jiǎn)要的看幾個(gè)例子,分析下其是否生效,即生效的前提,每個(gè)例子的答案選項(xiàng)都有4個(gè):
- always works
- only works with ARC
- only works without ARC
- never works
Example A:
void exampleA() {
char a = 'A';
^{
printf("%cn", a);
}();
}
Example B:
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
Example C:
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("Cn");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
Example D:
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%cn", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
Example E:
typedef void (^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%cn", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
- A:always works,此block類型是_NSConcreteStackBlock,但block調(diào)用在函數(shù)體內(nèi),函數(shù)釋放前已經(jīng)執(zhí)行完畢,所以無(wú)論在棧上還是堆上都可以正常執(zhí)行。
- B:only works with ARC,非ARC的話,block內(nèi)存空間在函數(shù)exampleB_addBlockToArray的棧上,此函數(shù)執(zhí)行完畢,退棧時(shí)候內(nèi)存空間清空,引用報(bào)錯(cuò)。ARC下會(huì)被轉(zhuǎn)換成_NSConcreteMallocBlock,copy到堆上所以生效。
- C:always works,此block類型是_NSConcreteGlobalBlock,存儲(chǔ)在數(shù)據(jù)區(qū),所以一直生效。
- D:only works with ARC,同B,但編譯器無(wú)法編譯,會(huì)報(bào)錯(cuò)。
- E:only works with ARC,同D,但編譯器不會(huì)報(bào)錯(cuò),更需注意。