2.1 Blocks概要
2.1.1 什么是Blocks
Blocks是C語言的擴(kuò)充功能懊渡。是帶有自動變量(局部變量)的匿名函數(shù)遭商。
匿名函數(shù)
C語言的函數(shù)
// 聲明函數(shù)名稱為func的函數(shù)
int func(int count);
// 通過函數(shù)名稱func調(diào)用函數(shù)
int result1 = func(10);
// 將函數(shù)func的地址賦值給指針類型變量funcptr
int (*funcptr)(int) = &func;
// 通過函數(shù)指針調(diào)用函數(shù)
int result2 = (*funcptr)(10);
C語言的函數(shù)都會必須有函數(shù)的名稱,而通過Blocks蚜枢,源代碼中能夠使用匿名函數(shù),即不帶名稱的函數(shù)。
帶有自動變量
C語言的函數(shù)中可能使用的變量
- 自動變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
其中主届,在函數(shù)的多次調(diào)用之間能夠傳遞值的變量有:
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
雖然這些變量的作用域不同,但在整個程序中待德,一個變量總保持在一個內(nèi)存區(qū)域君丁。因此,雖然多次調(diào)用函數(shù)将宪,但總量值總能保持不變绘闷,在任何時候以任何狀態(tài)調(diào)用,使用的都是同樣的變量的值较坛。
Blocks提供了保持自動變量值的方法印蔗。
2.2 Blocks模式
2.2.1 Block語法
完整形式的Block語法與一般的C語言函數(shù)定義相比,僅有兩點(diǎn)不同:
(1) 沒有函數(shù)名
(2) 帶有“^”丑勤。
Block語法BN范式
Block_literal_expression ::= ^ block_decl compound_statement_body
block_decl ::=
block_decl ::= parameter_list
block_decl ::= type_expression
??
^int (int count){return count + 1;}
返回值類型可以省略华嘹。如果聲明返回值類型,返回值類型需要跟表達(dá)式中return語句中的返回值類型一致法竞,如果表達(dá)式中無return語句可以使用void類型耙厚。如果表達(dá)式中有多個return語句强挫,所有return的返回值類型必須相同。
??
^(int count){return count + 1;}
如果不使用參數(shù)颜曾,參數(shù)列表也可以省略纠拔。
??
^{printf("Blocks");}
2.2.2 Block類型變量
在C語言中,可以將所定義的函數(shù)的地址賦值給函數(shù)指針類型的變量泛豪。同樣地稠诲,在Block語法下,可將Block語法賦值給聲明的Block類型的變量中诡曙。
聲明Block類型變量的示例如下:
int (^blk)(int);
該Block類型變量與一般C語言變量完全相同臀叙,可作為一下用途使用:
- 自動變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
使用Block語法將Block賦值為Block類型變量
int (^blk)(int) = ^(int count){return count + 1;};
變量blk與通常的變量相同,所以也可以由Block類型變量向Block類型變量賦值价卤,可以向函數(shù)傳遞Block劝萤,也可以將Block作為函數(shù)的返回值。在函數(shù)參數(shù)和返回值中使用Block類型變量時慎璧,記述方式極為復(fù)雜床嫌,我們可以像使用函數(shù)指針類型時那樣,使用typedef來解決該問題胸私。
// 定義一個返回值類型是int 別名為blk_t 有一個int類型參數(shù) 的Block
typedef int (^blk_t)(int);
Block類型的變量可完全像通常的C語言變量一樣使用厌处,因此也可以使用指向Block類型變量的指針,即Block指針類型變量岁疼。
// C語言中可以這么使用
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1;};
blk_t *blkptr = &blk;
(*blkptr)(10);
2.2.3 截獲自動變量的值
Blocks中阔涉,Block表達(dá)式截獲所使用的自動變量的值是自動變量的瞬間值(保存在Block結(jié)構(gòu)體中)。
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
執(zhí)行結(jié)果
val = 10
2.2.4 __block說明符
自動變量的值截獲只能保存執(zhí)行Block語法的瞬間值捷绒。保存后就不能改寫該值瑰排。
若想在Block語法的表達(dá)式中將值賦給在Block語法外聲明的自動變量,需要在該自動變量上加__block
說明符暖侨。
2.2.5 截獲的自動變量
賦值給截獲的自動變量的操作會產(chǎn)生編譯錯誤椭住,但使用截獲的值卻不會有任何問題。
賦值操作
id array = [NSMutableArray array];
void (^blk)(void) = ^{
array = [NSMutableArray array];
};
blk();
編譯器報錯
Variable is not assignable (missing __block type specifier)
使用操作
id array = [NSMutableArray array];
void (^blk)(void) = ^{
[array addObject:@1];
};
blk();
編譯器編譯成功字逗。
需要注意的是現(xiàn)在的Blocks中函荣,截獲自動變量的方法并沒有實(shí)現(xiàn)對C語言數(shù)組的截獲,這時使用指針可以解決該問題扳肛。
?
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n",text[2]);
};
blk();
error
Cannot refer to declaration with an array type inside block
√
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n",text[2]);
};
blk();
2.3 Blocks的實(shí)現(xiàn)
2.3.1 Block的實(shí)質(zhì)
我們可以通過clang編譯器將含有Block語法的源代碼轉(zhuǎn)換為C++源代碼傻挂。
cd到目標(biāo)文件夾,執(zhí)行語句
clang -rewrite-objc 源代碼文件名
源碼
#include <stdio.h>
int main()
{
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
return 0;
}
該源碼可通過clang轉(zhuǎn)換為以下形式:
// 結(jié)構(gòu)體 &:取地址運(yùn)算符 *:指針運(yùn)算符
struct __block_impl {
void *isa;
int Flags; // 標(biāo)志
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們來一步步解讀上面的轉(zhuǎn)換后的代碼
源代碼的Block語法
^{printf("Block\n");}
轉(zhuǎn)換為
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
可以看出Blocks使用的匿名函數(shù)實(shí)際被作為C語言函數(shù)來處理挖息,根據(jù)Block語法所屬的的函數(shù)名(main)和Block語法在該函數(shù)出現(xiàn)的順序值(0)來給clang變換的函數(shù)命名金拒。
該函數(shù)的參數(shù)__self
相當(dāng)于C++實(shí)例方法中指向?qū)嵗陨淼淖兞縯his,或是Objective-C實(shí)例方法中指向?qū)ο笞陨淼淖兞縮elf,即參數(shù)__self
為指向Block值的變量绪抛。
這里的參數(shù)__self
是__main_block_impl_0
結(jié)構(gòu)體的指針资铡。
綜上,Block結(jié)構(gòu)體就是__main_block_impl_0
結(jié)構(gòu)體幢码。Block的值就是通過__main_block_impl_0
構(gòu)造出來的笤休。
__main_block_impl_0
結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl; // 成員變量
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0
結(jié)構(gòu)體有成員變量impl、Desc和構(gòu)造函數(shù)三部分組成症副。
__block_impl
結(jié)構(gòu)體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl
結(jié)構(gòu)體成員變量如下:
- isa:
- Flags:標(biāo)志
- Reserved:今后版本升級所需的區(qū)域
- *FuncPtr:函數(shù)指針
__main_block_desc_0
結(jié)構(gòu)體
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__main_block_desc_0
結(jié)構(gòu)體成員變量如下:
- reserved:今后版本升級所需的區(qū)域
- Block_size: Block的大小
初始化__main_block_impl_0
結(jié)構(gòu)體的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
__main_block_imlp_0
結(jié)構(gòu)體就相當(dāng)于Objective-C類對象的結(jié)構(gòu)體店雅,這里的_NSConcreteStackBlock
相當(dāng)于Block的結(jié)構(gòu)體實(shí)例,也就是說block其實(shí)就是Objective-C對于閉包的對象實(shí)現(xiàn)。
Objective-C中由類生成對象就是像結(jié)構(gòu)體這樣“生成由該類生成的對象的結(jié)構(gòu)體實(shí)例”贞铣。生成的各個對象闹啦,即由該類生成的對象的各個結(jié)構(gòu)體實(shí)例,通過成員變量isa保持該類對的結(jié)構(gòu)體實(shí)例指針辕坝。
各類的結(jié)構(gòu)體是基于objc_class
結(jié)構(gòu)體的class_t
結(jié)構(gòu)體窍奋。class_t
結(jié)構(gòu)體在objc4運(yùn)行時庫的runtime/objc-runtime-new.h中聲明如下:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
在Objective-C中,比如NSObject的class_t
結(jié)構(gòu)體實(shí)例以及NSMutableArray的class_t
結(jié)構(gòu)體實(shí)例等酱畅,均生成并保持各個類的class_t
結(jié)構(gòu)體實(shí)例琳袄。該實(shí)例持有聲明的成員變量、成員的名稱纺酸、方法的實(shí)現(xiàn)(即函數(shù)指針)窖逗、屬性、以及父類的指針吁峻,并被Objective-C運(yùn)行時庫所使用。
如果展開__main_block_impl_0
結(jié)構(gòu)體的__block_impl
結(jié)構(gòu)體在张,可記述為如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *Funcptr;
struct __main_block_desc_0* Desc;
};
該結(jié)構(gòu)體構(gòu)造函數(shù)會像下面這樣進(jìn)行初始化
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
Funcptr = __main_block_impl_0用含;
Desc = & __main_block_desc_0_DATA;
此__main_block_impl_0
結(jié)構(gòu)體相當(dāng)于基于objc_object
結(jié)構(gòu)體的Objective-C類對象的結(jié)構(gòu)體_NSConcreteStackBlock
相當(dāng)于class_t
結(jié)構(gòu)體實(shí)例。在將Block作為Objective的對象處理時帮匾,關(guān)于該類的信息放置于_NSConcreteStackBlock
中啄骇。
Block實(shí)質(zhì)就是對象。
2.3.2 截獲自動變量值
源碼
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void(^blk)(void) = ^{printf(fmt,val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
該源碼可通過clang轉(zhuǎn)換為以下形式:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "These values were changed. val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們可以看到Block語法表達(dá)式中使用的自動變量被作為成員變量追加到__main_block_impl_0
結(jié)構(gòu)體中瘟斜。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
}
結(jié)構(gòu)體內(nèi)聲明的成員變量類型與自動變量類型完全相同缸夹,而Block語法表達(dá)式中沒有使用的自動變量不會被追加,如源代碼中的變量dmy螺句。
總的來說虽惭,所謂“截獲自動變量的值”意味著在執(zhí)行Block語法時,Block語法表達(dá)式所使用的自動變量的值被保存到Block的結(jié)構(gòu)體實(shí)例(即Block自身)中蛇尚。
2.3.3 __block說明符
當(dāng)我們嘗試像下面這種方式改變Block中的自動變量val時
int val = 0;
void(^blk)(void) = ^{val = 1;};
因?yàn)樵趯?shí)現(xiàn)上不能改寫被截獲的自動變量的值芽唇,所以編譯器會報錯
Variable is not assignable (missing __block type specifier)
而我們有時需要在Block中改變外部變量的值,有兩種解決方法。
第一種
C語言中有一個變量匆笤,允許Block改寫值研侣。具體如下:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
雖然Block語法的匿名函數(shù)部分簡單地變換為C語言函數(shù),但從這個變換的函數(shù)訪問靜態(tài)全局變量/全局變量沒有任何改變炮捧,可直接使用庶诡。
但是靜態(tài)變量的情況下,轉(zhuǎn)換后的函數(shù)原本就設(shè)置在含有Block語法的函數(shù)外(使用靜態(tài)變量的指針進(jìn)行訪問)咆课,所以無法從變量作用域訪問末誓。
實(shí)際上,在由Block語法生成的值Block上傀蚌,可以存有超過其變量作用域的被截獲對象自動變量基显。變量作用域結(jié)束的同時,原來的自動變量被廢棄善炫,因此Block中超過變量作用域而存在的變量同靜態(tài)變量一樣撩幽,將不能通過指針訪問原來的自動變量。
第二種
使用__bloc
k說明符
C語言中有以下存儲類說明符:
- typedef
- extern
- static
- auto
- register
__block
說明符類似于static箩艺、auto窜醉、register說明符,他們用于指定將變量值設(shè)置到哪個存儲域中艺谆。例如榨惰,auto表示作為自動變量存儲在棧中,static表示靜態(tài)變量存儲在數(shù)據(jù)區(qū)中静汤。
當(dāng)我們在自動變量聲明上追加__block
說明符
int __block val = 0;
void(^blk)(void) = ^{val = 1;};
該源碼可通過clang轉(zhuǎn)換為以下形式:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
}
可以發(fā)現(xiàn)__block
變量也同Block一樣變成了__Block_byref_val_0
結(jié)構(gòu)體類型的自動變量琅催,即棧上生成的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例。該變量初始化為1虫给,且這個值也出現(xiàn)在結(jié)構(gòu)體實(shí)例的初始化中藤抡,這意味著該結(jié)構(gòu)體持有相當(dāng)于原自動變量的成員變量。
該結(jié)構(gòu)體聲明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
再看下__block
變量賦值的代碼:
^{val = 1;}
轉(zhuǎn)化為
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
向__block
變量賦值抹估,Block的__main_block_impl_0
結(jié)構(gòu)體實(shí)例持有指向__block
變量的__Block_byref_val_0
結(jié)構(gòu)體實(shí)例的指針缠黍。
__Block_byref_val_0
結(jié)構(gòu)體實(shí)例的成員變量__forwarding
持有指向該實(shí)例自身的指針。通過成員變量__forwarding
訪問成員變量val药蜻。(成員變量val是該實(shí)例自身持有的變量瓷式,它相當(dāng)于原自動變量。)
2.3.4 Block存儲域
Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量语泽,__block
變量轉(zhuǎn)換為__block
變量的結(jié)構(gòu)體類型的自動變量贸典。所謂結(jié)構(gòu)體類型的自動變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例踱卵。
Block與__block變量的實(shí)質(zhì)
名稱 | 實(shí)質(zhì) |
---|---|
Block | 棧上Block的結(jié)構(gòu)體實(shí)質(zhì) |
__block變量 | 棧上__block變量的結(jié)構(gòu)體實(shí)例 |
Block的類
類 | 設(shè)置對象的存儲域 |
---|---|
_NSConcreteStackBlock | 棧 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域(.data區(qū)) |
_NSConcreteMallocBlock | 堆 |
_NSConcreteGlobalBlock
在記敘全局變量的地方使用Block語法時瓤漏,生成的Block為_NSConcreteGlobalBlock
類對象。例如:
void (^blk)(void) = ^{printf("Global Block");};
int main()
{
該源碼通過聲明全局變量blk來使用Block語法。如果轉(zhuǎn)換源代碼蔬充,Block用結(jié)構(gòu)體的成員變量isa的初始化如下:
impl.isa = & _NSConcreteGlobalBlock
該Block的類為_NSConcreteGlobalBlock
類蝶俱。此Block即該Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域中。因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣幼兞考⒙圆淮嬖趯ψ詣幼兞窟M(jìn)行截獲榨呆。由此Block用結(jié)構(gòu)體實(shí)例的內(nèi)容不依賴于執(zhí)行時的狀態(tài),所以整個程序中只需一個實(shí)例庸队。因此將Block用結(jié)構(gòu)體實(shí)例設(shè)置在全局變量相同的數(shù)據(jù)區(qū)域即可积蜻。
即使在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時,只要Block語法不截獲自動變量彻消,就可以將Block設(shè)置在程序的數(shù)據(jù)區(qū)域竿拆。
- 記述全局變量的地方有Block語法時
- Block語法的表達(dá)式中不使用應(yīng)截獲的自動變量時
以上兩種情況下,Block為_NSConcreteGlobalBlock
類對象宾尚。即Block配置在程序的數(shù)據(jù)區(qū)域中丙笋。除此之外的Block語法生成的Block為_NSConcreteStackBlock
類對象,且設(shè)置在棧上煌贴。
_NSConcreteMallocBlock
配置在全局變量上的Block御板,從變量作用域外可以通過指針安全地使用。但設(shè)置在棧上的Block牛郑,如果其所屬的變量作用域結(jié)束怠肋,該Block就被廢棄。由于__block
變量也配置在棧上淹朋,同樣地笙各,如果其所屬的變量作用域結(jié)束,則該__block
變量也被廢棄础芍。
Blocks提供了將Block和__block
變量從棧上復(fù)制到堆上的方法來解決這個問題杈抢。將配置在棧上的Block復(fù)制到堆上,這樣即使Block語法記述的變量作用域結(jié)束者甲,堆上的Block還可以繼續(xù)存在春感。
復(fù)制到堆上的Block將_NSConcreteMallocBlock
類對象寫入Block用結(jié)構(gòu)體實(shí)例的成員變量isa砌创。
impl.isa = & _NSConcreteMallocBlock;
__block
變量用結(jié)構(gòu)體成員變量__forwarding
可以實(shí)現(xiàn)無論__block
變量配置在棧上還是在堆上時都可能夠正確地訪問__block
變量虏缸。
除了向方法或函數(shù)的參數(shù)傳遞Block時,編譯器都會自動生成將Block從棧上復(fù)制到堆上的代碼嫩实。
但是如果在方法或函數(shù)中適當(dāng)?shù)貜?fù)制了傳遞過來的參數(shù)刽辙,那么就不必在調(diào)用該方法或函數(shù)前手動復(fù)制了。以下方法或函數(shù)不用手動復(fù)制甲献。
- Cocoa框架的方法且方法名中含有usingBlock等時
- GCD的API
將Block從棧上復(fù)制到堆上是相當(dāng)消耗COU的宰缤,當(dāng)Block設(shè)置在棧上也能夠使用時,將Block復(fù)制到堆上只是在浪費(fèi)CPU資源。因此只在block作為參數(shù)傳遞時手動調(diào)用copy方法慨灭。
Block副本
Block的類 | 副本源的配置存儲域 | 復(fù)制效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加 |
2.3.5 __block變量存儲域
Block從棧復(fù)制到堆時對__block產(chǎn)生的影響
__block變量的配置存儲域 | Block從棧復(fù)制到堆時的影響 |
---|---|
棧 | 從棧復(fù)制到堆并被Block持有 |
堆 | 被Block持有 |
若在1個Block中使用__block
變量朦乏,Block從棧上復(fù)制到堆上,這些__block
變量也全部從棧復(fù)制到堆上氧骤。此時呻疹,Block持有__block
變量。Block已復(fù)制到堆上的情形下筹陵,復(fù)制Block對所使用的__block
變量沒有任何影響刽锤。
在多個Block中使用__block
變量,任何一個Block從棧復(fù)制到堆時朦佩,__block
變量也會一并從棧復(fù)制到堆并被該Block所持有并思。當(dāng)剩下的Block從棧復(fù)制到堆時,被復(fù)制的Block持有__block
變量语稠,并增加__block
變量的引用計數(shù)宋彼。
如果配置在堆上的Block被廢棄,那么它所使用的__block
變量就被釋放颅筋。
使用__block
變量用結(jié)構(gòu)體成員變量__forwarding
的原因
通過Block的復(fù)制宙暇,__block
變量從棧復(fù)制到堆。此時可同時訪問棧上的__block
變量和堆上的__block
變量议泵。源代碼如下:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
利用copy方法復(fù)制了使用了__block
變量的Block語法占贫。Block和__block
變量均是從棧復(fù)制到堆。此代碼中在Block語法表達(dá)式中使用了初始化的__block
變量先口。
^{++val;}
然后在Block語法之后使用了與Block無關(guān)的變量型奥。
++val;
以上兩種源代碼均可轉(zhuǎn)換為如下的形式:
++(val.__forwarding->val);
棧上的__block
變量用結(jié)構(gòu)體實(shí)例在__block
變量從棧上復(fù)制到堆上時,會將成員變量__forwarding
的值替換為復(fù)制目標(biāo)堆上的__block
變量用結(jié)構(gòu)體實(shí)例的地址碉京。
這樣厢汹,無論是在Block語法中、Block語法外面使用__block
變量谐宙,還是__block
變量配置在椞淘幔或堆上,都可以順利地訪問同一個__block
變量凡蜻。
2.3.6 截獲對象
調(diào)用copy函數(shù)和dispose函數(shù)的時機(jī)
函數(shù) | 調(diào)用時機(jī) |
---|---|
copy函數(shù) | 棧上的Block復(fù)制到堆上時 |
dispose函數(shù) | 堆上的Block被廢棄時 |
Block會復(fù)制到堆上的時機(jī):
- 調(diào)用Block的copy實(shí)例方法時
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時
截獲對象時和使用__block變量時的不同
對象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block變量 | BLOCK_FIELD_IS_BYREF |
Block中使用對象類型自動變量時搭综,除以下情景外,推薦調(diào)用Block的copy實(shí)例方法划栓。
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時
2.3.7 __block變量和對象
__block
說明符可指定任何類型的自動變量兑巾。
__block id obj = [[NSObject alloc] init];
等同于
__block id __strong obj = [[NSObject alloc] init];
ARC有效時,id類型以及對象類型變量必定附加所有權(quán)修飾符忠荞,缺省為附有__strong
修飾符的變量蒋歌。
在__block
變量為附有__strong
修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下帅掘,當(dāng)__block
變量從棧復(fù)制到堆上時,使用_Block_object_assign
函數(shù)持有賦值給__block
變量的對象堂油。當(dāng)堆上的__block
變量被廢棄時修档,使用__Block_object_dispose
函數(shù),釋放賦值給__block
變量的對象府框。
由此可知萍悴,即使對象賦值復(fù)制到堆上的附有__strong
修飾符的對象類型__block
中,只要__block
變量在堆上繼續(xù)存在寓免,那么該對象就回繼續(xù)處于被持有的狀態(tài)癣诱。這與Block中使用賦值給附有__strong
修飾符的對象類型自動變量的對象相同。
2.3.8 Block循環(huán)引用
如果在Block中使用附有__strong
修飾符的對象類型的自動變量袜香,那么當(dāng)Block從棧復(fù)制到堆上時撕予,該對象為Block所持有。這樣容易引起循環(huán)引用蜈首。
typedef void(^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^{NSLog(@"self = %@",self);};
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
該源代碼中MyObject中dealloc實(shí)例方法一定沒有被調(diào)用实抡。
MyObject類對象的Block類型成員變量blk_
持有賦值為Block的強(qiáng)引用。即MyObject類對象持有Block欢策。init實(shí)例方法中執(zhí)行的Block語法使用附有__strong
修飾符的id類型變量self吆寨。并且由于Block語法賦值在了成員變量blk_
中,因此通過Block語法聲稱在棧上的Block此時由棧復(fù)制到堆踩寇,并持有所使用的self啄清。self持有Block,Block持有self俺孙。造成了循環(huán)引用辣卒。
為了避免此循環(huán)引用,可聲明附有__weak
修飾符的變量睛榄,并將self賦值使用荣茫。
- (instancetype)init {
self = [super init];
id __weak tmp = self;
blk_ = ^{NSLog(@"self = %@",tmp);};
return self;
}
也可以使用__block
變量來避免循環(huán)引用。
typedef void(^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@implementation MyObject
- (id)init {
self = [super init];
__block id tmp = self;
blk_ = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
return self;
}
- (void)execBlock {
blk_();
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
@end
該源代碼沒有引起循環(huán)引用场靴。但是如果不調(diào)用execBlock實(shí)例方法啡莉,即不執(zhí)行賦值給成員變量blk_
的Block,便會循環(huán)引用并引起內(nèi)存泄露旨剥。在生成并持有MyObject類對象的狀態(tài)會引起一下循環(huán)引用咧欣。
- MyObject類對象持有Block
- Block持有
__block
變量 -
__block
變量持有MyObject類對象
如果不執(zhí)行execBlock實(shí)例方法,就會持續(xù)該循環(huán)引用從而造成內(nèi)存泄露泞边。
通過執(zhí)行execBlock實(shí)例方法该押,Block被執(zhí)行疗杉,nil被賦值在__block變量tmp中阵谚。
blk_ = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
因此蚕礼,__block變量tmp對MyObject類對象的強(qiáng)引用失效。避免循環(huán)引用的過程如下所示:
- MyObject類對象持有Block
- Block持有
__block變量
使用__block變量避免循環(huán)引用的優(yōu)點(diǎn)如下:
- 通過
__block
變量可控制對象的持有期間 - 在不能使用
__weak
修飾符的環(huán)境中不使用__unsafe_unretained
修飾符即可(不必?fù)?dān)心懸垂指針)梢什。在執(zhí)行Block時可動態(tài)地決定是否將nil或其他對象賦值在__block變量中奠蹬。
使用__block
變量的缺點(diǎn)如下:
- 為避免循環(huán)引用必須執(zhí)行Block
2.3.9 copy/release
ARC無效時,一般需要手動將Block從棧復(fù)制到堆嗡午。另外囤躁,由于ARC無效,所以肯定要釋放復(fù)制的Block荔睹。這時我們用copy實(shí)例方法用來復(fù)制狸演,用release實(shí)例方法來釋放。