block 實際上是OC 對閉包closure的實現(xiàn)
block的數(shù)據(jù)結(jié)構(gòu)
先來看下block的結(jié)構(gòu)示意圖:
各組成部分含義:
- isa:該對象是什么
- flags:block附加信息
- reserve:保留變量
- invoke:函數(shù)實現(xiàn)指針
- descriptor:block 描述符暗赶,主要是block的一些附加信息
- variables:capture過來的變量
這樣來看的話碉渡,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光绕,不會訪問任何外部變量。
- _NSConcreteStackBlock 保存在棧中的 block畜份,當(dāng)函數(shù)返回時會被銷毀诞帐。
- _NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計數(shù)為 0 時會被銷毀爆雹。
以下說明幾點需要注意的:
- _NSConcreteGlobalBlock 是全局靜態(tài)block停蕉,結(jié)構(gòu)體存儲在數(shù)據(jù)區(qū)愕鼓。
- 常見的是有捕獲外部變量的_NSConcreteStackBlock,需要注意的是如果這種類型的block 定義在函數(shù)內(nèi)部慧起,當(dāng)函數(shù)執(zhí)行完畢菇晃,退棧的時候會將該block結(jié)構(gòu)體所占的內(nèi)存空間釋放掉,這樣再引用的話會報錯蚓挤。
- _NSConcreteMallocBlock 通常不會在源碼中直接出現(xiàn)磺送,OC ARC下會對_NSConcreteStackBlock 進(jìn)行優(yōu)化,將其copy到堆上灿意,轉(zhuǎn)換成_NSConcreteMallocBlock估灿,所以無特殊處理,OC中將只會有1缤剧,3兩種類型block
- _NSConcreteStackBlock捕獲的局部變量馅袁,如不加_block修飾符,將會把變量copy一份到其結(jié)構(gòu)體中荒辕,所以才會在內(nèi)部修改不影響外部變量汗销,加_block修飾之后,結(jié)構(gòu)體中會添加一個__Block_byref_i_0 的結(jié)構(gòu)體抵窒,且復(fù)制的是變量地址弛针,達(dá)到可以修改外部變量的效果。關(guān)于這部分詳細(xì):這里
block的內(nèi)存測試
下面簡要的看幾個例子李皇,分析下其是否生效钦奋,即生效的前提,每個例子的答案選項都有4個:
- 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í)行完畢圃阳,所以無論在棧上還是堆上都可以正常執(zhí)行。
- B:only works with ARC璧帝,非ARC的話捍岳,block內(nèi)存空間在函數(shù)exampleB_addBlockToArray的棧上,此函數(shù)執(zhí)行完畢睬隶,退棧時候內(nèi)存空間清空锣夹,引用報錯。ARC下會被轉(zhuǎn)換成_NSConcreteMallocBlock苏潜,copy到堆上所以生效银萍。
- C:always works,此block類型是_NSConcreteGlobalBlock恤左,存儲在數(shù)據(jù)區(qū)贴唇,所以一直生效搀绣。
- D:only works with ARC,同B戳气,但編譯器無法編譯链患,會報錯。
- E:only works with ARC瓶您,同D麻捻,但編譯器不會報錯,更需注意呀袱。