一. Blocks的認(rèn)識(shí)
什么是Blocks?
Blocks是C語言的拓展功能(帶有局部變量的匿名函數(shù))。局部變量跟block的關(guān)系就像成員變量和類。
所謂匿名函數(shù),就是不帶名稱的函數(shù)。C語言的標(biāo)準(zhǔn)是不存在這樣的函數(shù)的。
//聲明一個(gè)函數(shù)
int func( int count);
//直接調(diào)用函數(shù)時(shí)
int result = func(10);
//用函數(shù)指針調(diào)用函數(shù)時(shí)
int result =(* func)(10);
在C語言中,不使用想賦值的函數(shù)的名稱,就無法取得該函數(shù)的地址。而通過Blocks,就能夠使用不帶名稱的函數(shù)。
Block的語法
完整的Block語法與C語言函數(shù)相比有兩點(diǎn)不同:沒有函數(shù)名和帶有“^”。
^ 返回值類型 參數(shù)列表 表達(dá)式
^ int (int count){return count + 1;}
但是Block語法可以省略返回值類型和參數(shù)列表。省略返回值類型時(shí),如果表達(dá)式中有return語句就使用該返回值的類型,如果沒有return語句就使用void類型。

//省略后的Block
^{printf("Blocks\n");}
Blocks的使用
我們先來看看C語言函數(shù)的使用:
int func(int count){
return count + 1;
}
//將函數(shù)地址賦值給函數(shù)指針類型變量
int (*funcptr)(int) = &func;
聲明Block類型變量以及變量之間的賦值:
int (^blk)(int) = ^(int count){return count + 1};
int (^blk1)(int);
blk1 = blk;
Block類型變量作為函數(shù)參數(shù)和函數(shù)返回值:
int (^func())(int){
return ^(int count){return count + 1};
}
使用typedef聲明Block類型變量
typedef int (^blk_t)(int);
int func(blk_t blk){
return blk(10);
}
也可以像C語言變量一樣使用,使用Block的指針類型變量。
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1};
blk_t *blkptr = &blk;
(*blkptr)(10);
Block截獲的自動(dòng)變量值
Block表達(dá)式截獲所使用的自動(dòng)變量的值是該自動(dòng)變量的瞬間值,不能在Block中改變(編譯器會(huì)報(bào)錯(cuò)),只能輸出該瞬間值。如果在Block中修改該值,要加上__block修飾符。
int let = 10;
__block int var = 20;
void (^blk)(void) = ^{
printf("%d,%d",let,var); //輸出結(jié)果 10,21
var = 22; //這邊也可以改變var的值,而let不可以
};
let = 11;
var = 21;
blk();
二. Block的底層實(shí)現(xiàn)
通過clang(LLVM編譯器)將Block語法的源代碼轉(zhuǎn)換為C++源代碼來了解Block的底層實(shí)現(xiàn)。
- 在Xcode創(chuàng)建Block.m文件
- 打開終端,cd到存放Block.m的文件夾
- 輸入clang -rewrite-objc Block.m,生成Block.cpp
int mian(){
void (^blk)(void) = ^{
printf("Block");
};
blk();
return 0;
}
打開.cpp拉到最后面,這些才是我們轉(zhuǎn)換后的源碼:
struct __block_impl {
void *isa; //對象都有的isa指針,指向元類
int Flags; //標(biāo)志
int Reserved; //今后版本升級(jí)所需的區(qū)域
void *FuncPtr; //函數(shù)指針
};
/* block結(jié)構(gòu)體 */
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
// block構(gòu)造函數(shù)
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/* block中的代碼 */
//__cself相當(dāng)于Objective-C中的self(用OC類的說法就是self 執(zhí)行__mian_block_func_0)
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
printf("Block");
}
static struct __mian_block_desc_0 {
size_t reserved; //今后版本升級(jí)所需的區(qū)域
size_t Block_size; //Block的大小
} __mian_block_desc_0_DATA = { //這部分為賦值
0,
sizeof(struct __mian_block_impl_0)
};
/* mian函數(shù) */
int mian(){
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們先看看main函數(shù)里面的代碼,我們可簡化成以下代碼:
//創(chuàng)建Block
struct __mian_block_impl_0 temp = __mian_block_impl_0(__mian_block_func_0, &__mian_block_desc_0_DATA));
struct __mian_block_impl_0 *blk = &tmp;
//執(zhí)行Block
(*blk->impl.FuncPtr)(blk);
該源代碼將__mian_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__mian_block_impl_0結(jié)構(gòu)體實(shí)例的指針,賦值給__mian_block_impl_0結(jié)構(gòu)體指針類型的變量blk。通俗地講,就是將__mian_block_impl_0結(jié)構(gòu)體實(shí)例的指針賦給變量blk。
__mian_block_impl_0構(gòu)造函數(shù)(即源代碼中的Block構(gòu)造函數(shù))的第一個(gè)參數(shù)是Block語法轉(zhuǎn)換的C語言指針,第二個(gè)參數(shù)是作為靜態(tài)全局變量初始化的__mian_block_desc_0結(jié)構(gòu)體實(shí)例指針。
我們再來看執(zhí)行Block的部分,這是簡單地使用函數(shù)指針調(diào)用函數(shù)。在構(gòu)造函數(shù)中,由Block語法轉(zhuǎn)換的__mian_block_func_0函數(shù)的指針被賦值給成員變量FuncPtr,并以Block自身為參數(shù)。
補(bǔ)充重要的一點(diǎn),前面提到的:
impl.isa = &_NSConcreteStackBlock;
其實(shí),block就是Objective-C對象。這邊isa指針指向的_NSConcreteStackBlock,該Block的信息放置于_NSConcreteStackBlock中(相當(dāng)于子類和父類的關(guān)系)。
截獲自動(dòng)變量值
我們先來看看截獲自動(dòng)變量值的源代碼,理解自動(dòng)變量值為什么只是一個(gè)瞬間值。
Objective-C代碼:
int mian(){
int val = 10;
void (^blk)(void) = ^{
printf("val = %d",val); //輸出結(jié)果 val= 10
};
val = 11;
blk();
return 0;
}
clang后的源代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
/ * Block結(jié)構(gòu)體 */
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int val; //比之前的結(jié)構(gòu)多出該成員變量val
/* 構(gòu)造函數(shù) */
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/ * Block中執(zhí)行的函數(shù) */
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("val = %d",val); //打印的是Block自己的成員變量的值
}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
int mian(){
int val = 10;
//構(gòu)造函數(shù)中傳入val的值
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, val));
val = 11;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通過源碼,我們看出,Block語法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到__mian_block_impl_0結(jié)構(gòu)體中,并且會(huì)從構(gòu)造函數(shù)中傳入val的值作為參數(shù)。改寫該值報(bào)編譯錯(cuò)誤的原因我們也由想而知。
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int val; //比之前的結(jié)構(gòu)多出該成員變量val
/* 構(gòu)造函數(shù) */
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在后面的輸出函數(shù)中,也是輸出Block的成員變量val的值。由此可見,在__mian_block_impl_0結(jié)構(gòu)體實(shí)例中,自動(dòng)變量值被截獲。
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("val = %d",val); //打印的是Block自己的成員變量的值
}
__blcok說明符的值
那如何才能改變Block中截獲的自動(dòng)變量?有兩種方法:
① C語言的靜態(tài)變量、靜態(tài)全局變量、全局變量允許Block改寫值
int global_val = 1; //全局變量
static int static_global_val = 2; //靜態(tài)全局變量
int mian(){
static int static_val = 10; //靜態(tài)變量
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *=3;
};
blk();
return 0;
}
轉(zhuǎn)換后的源碼為:
int global_val = 1;
static int static_global_val = 2;
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
int *static_val; //保存的是靜態(tài)變量的指針
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *=3;
}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};
int mian(){
static int static_val = 10;
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
對靜態(tài)全局變量static_global_val和全局變量global_val的訪問和轉(zhuǎn)換前完全相同。
而靜態(tài)變量static_val,是使用其指針進(jìn)行訪問的。將靜態(tài)變量static_val的指針傳遞給__mian_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存。那為什么自動(dòng)變量不用指針訪問的方法呢?因?yàn)樽詣?dòng)變量存放在棧幀中,變量作用域結(jié)束的同時(shí),自動(dòng)變量被廢棄,Block將不能用指針訪問到原來的自動(dòng)變量。
② 使用__block說明符
__block說明符類似于static、auto和register說明符,它們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。例如,auto表示作為自動(dòng)變量存儲(chǔ)在棧中,static表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中。
int mian(){
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
轉(zhuǎn)換后的源代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
/* __block變量生成的結(jié)構(gòu)體 */
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; //保存的val的值
};
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__mian_block_impl_0(void *fp, struct __mian_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 __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __mian_block_copy_0(struct __mian_block_impl_0*dst, struct __mian_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __mian_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __mian_block_impl_0*, struct __mian_block_impl_0*);
void (*dispose)(struct __mian_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __mian_block_impl_0),
__mian_block_copy_0,
__mian_block_dispose_0
};
int mian(){
//創(chuàng)建__Block_byref_val_0結(jié)構(gòu)體實(shí)例
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
//將__Block_byref_val_0結(jié)構(gòu)體指針作為參數(shù)
void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
加上__block說明符,我們發(fā)現(xiàn)__block變量同Block一樣變成__Block_byref_val_0結(jié)構(gòu)體類型的自動(dòng)變量。__Block_byref_val_0結(jié)構(gòu)體的地址作為參數(shù)傳入Block的構(gòu)造函數(shù)中。因此,Block的__mian_block_impl_0結(jié)構(gòu)體實(shí)例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針。
至于__Block_byref_val_0結(jié)構(gòu)體為什么在Block外創(chuàng)建,是為了方便多個(gè)Block使用。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding; //指向自身的指針
int __flags;
int __size;
int val; //保存的val的值
};
在給__bloc變量賦值的源碼中:
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding指向該實(shí)例自身的指針。通過成員變量__forwarding訪問成員變量val。(這邊有點(diǎn)繞,要弄懂先要弄清楚Block的存儲(chǔ)域)

Block的存儲(chǔ)域
Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動(dòng)變量,__block變量轉(zhuǎn)換為__block變量的結(jié)構(gòu)體類型的自動(dòng)變量。所謂結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的該結(jié)構(gòu)體的實(shí)例。那Block超出變量作用域是如何繼續(xù)存在的呢?
要了解這點(diǎn),我們要先知道Block的三種類型以及它們的存儲(chǔ)域:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock

通過聲明全局變量blk來使用Block語法,那該Block類為_NSConcreteGlobalBlock。因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣?dòng)變量,所以不存在對自動(dòng)變量的截獲。因此將Block用結(jié)構(gòu)體實(shí)例設(shè)置在與全局變量相同的數(shù)據(jù)區(qū)域中即可。另外,在函數(shù)內(nèi)而不在記述廣域變量的地方使用Block語法時(shí),只要Block不截獲自動(dòng)變量,就可以將Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域。
綜上,以下情況Block為_NSConcreteGlobalBlock類對象,除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象,且設(shè)置在棧上。
- 記述全局變量的地方有Block語法時(shí)
-
Block語法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)
配置在全局變量的Block,從變量作用域外也可以通過指針安全地使用。但設(shè)置在棧上的Block,如果所屬的變量作用域結(jié)束,該Block和__block變量都會(huì)被廢棄。針對這個(gè)問題,Blocks提供了將Block和__block變量從棧上復(fù)制到堆上的方法。
從棧復(fù)制到堆上的Block與__block變量.png
typedef int (^blk_t)(int)
blk_t func(int rate){
return ^(int count){return rate * count};
}
blk_t func(int rate){
blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
/*
* 將配置在棧上的Block的結(jié)構(gòu)體實(shí)例賦值給tmp
*/
tmp = objc_retainBlock(tmp);
/*
* 等價(jià)于tmp = _Block_copy(tmp)
* 將棧上的Block賦值到堆上,并將堆上的地址作為指針賦值給tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 將堆上的Block對象注冊到autoreleasepool并返回
*/
}
將Block作為函數(shù)返回值時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。當(dāng)向方法或函數(shù)的參數(shù)中傳遞Block時(shí),需要我們手動(dòng)生成代碼,調(diào)用Block的copy方法。

不過以下方法或函數(shù)不用手動(dòng)調(diào)用:
- Cocoa框架的方法且方法名中含有usingBlock等時(shí)(比如NSArray類的enumerateObjectsUsingBlock實(shí)例方法)
- GCD的API
當(dāng)使用__block變量的Block從棧上復(fù)制到堆上時(shí),這些__block變量也全部被從棧復(fù)制到堆,Block持有__block變量。


使用__block變量的Block持有__block變量(__block變量作為Block的一個(gè)成員變量)。如果Block被廢棄,它所持有的__block變量也就被釋放。棧上的__block用結(jié)構(gòu)體實(shí)例在從棧復(fù)制到堆時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block用結(jié)構(gòu)體實(shí)例的地址。這樣不管__block變量配置在棧上還是堆上,都可以正確訪問該變量。

__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
兩個(gè)++val均可轉(zhuǎn)換成以下源碼:
++(val.__forwarding->val);
第一個(gè)val為復(fù)制到堆上的__block變量用結(jié)構(gòu)體實(shí)例,會(huì)通過指針訪問到自己。第二個(gè)val為復(fù)制前棧上__block變量用結(jié)構(gòu)體實(shí)例,通過指針訪問到堆上的__block變量用結(jié)構(gòu)體實(shí)例。通過該功能,都可以順利訪問同一個(gè)__block變量。
截獲對象
前面我們截獲的是普通值或帶__block說明符的值,那要是在Block語法中使用對象呢?
typedef void(^blk_t)(id obj);
blk_t blk;
void setupBlk(){
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
} copy];
}
int main(){
setupBlk();
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
return 0;
}
輸出結(jié)果為:
array count = 1
array count = 2
array count = 3
本來array的變量域結(jié)束的同時(shí),array被廢棄,其強(qiáng)引用失效,因此賦值給變量array的NSMutableArray類的對象必定被釋放并廢棄。但是代碼運(yùn)行正常,由此可見賦值給array的NSMutableArray類的對象在最后Block的執(zhí)行部分超出其變量作用域而存在。通過編譯器轉(zhuǎn)換后的源代碼如下:
typedef void(*blk_t)(id obj);
blk_t blk;
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
id array;
/* 構(gòu)造方法 */
__setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __setupBlk_block_func_0(struct __setupBlk_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9v_3rmg01ds0zj0gpwn04shlxwc0000gn_T_Block_097da3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src) {
//相當(dāng)于retain方法
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
//相當(dāng)于release方法
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __setupBlk_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __setupBlk_block_impl_0*, struct __setupBlk_block_impl_0*);
void (*dispose)(struct __setupBlk_block_impl_0*);
} __setupBlk_block_desc_0_DATA = {
0,
sizeof(struct __setupBlk_block_impl_0),
__setupBlk_block_copy_0,
__setupBlk_block_dispose_0
};
void setupBlk(){
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__setupBlk_block_impl_0((void *)__setupBlk_block_func_0, &__setupBlk_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
int main(){
setupBlk();
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
我們可以發(fā)現(xiàn),Block結(jié)構(gòu)體中截獲__strong修飾符的成員變量array:
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
id array; //等同于id __strong array
}
在ARC需要遵守的規(guī)則中,對象型變量不能作為C語言結(jié)構(gòu)體的成員變量。因?yàn)榫幾g器不知道何時(shí)進(jìn)行C語言結(jié)構(gòu)體的初始化和廢棄,不能很好地管理內(nèi)存。但是Objective-C的運(yùn)行時(shí)庫能夠準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī),因此Block用結(jié)構(gòu)體中即使含有附有__strong或__weak修飾符的變量,也能恰當(dāng)?shù)剡M(jìn)行初始化和廢棄。
在該Block源碼中,__setupBlk_block_desc_0中增加了copy和dispose函數(shù)指針的成員變量,以及作為指針賦值給該成員變量的__setupBlk_block_copy_0和__setupBlk_block_dipose_0方法。
__setupBlk_block_copy_0函數(shù)調(diào)用相當(dāng)于retain實(shí)例方法的函數(shù),將對象賦值在對象類型的結(jié)構(gòu)體成員變量中,__setupBlk_block_dipose_0函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù),釋放賦值在對象類型的結(jié)構(gòu)體成員變量中的對象。簡單說,就是對Block結(jié)構(gòu)體中成員變量array賦值和廢棄。當(dāng)然,在Block從棧復(fù)制到堆時(shí)以及堆上的Block被廢棄時(shí)才會(huì)調(diào)用這些函數(shù)。
如果在在array前面加上__block說明符呢?
轉(zhuǎn)換的源碼如下:
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id array;
};
struct __setupBlk_block_impl_0 {
struct __block_impl impl;
struct __setupBlk_block_desc_0* Desc;
__Block_byref_array_0 *array;
__setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src{
_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
同Block截獲對象一樣,當(dāng)__block變量從棧復(fù)制到堆時(shí),使用_Block_object_assign函數(shù),持有賦值給__block變量的對象。當(dāng)堆上的__block變量被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放賦值給__blcok變量的對象。
由此可見,只要堆上的Block的成員變量(__block變量或者對象)存在,那么該對象就會(huì)繼續(xù)處于被持有狀態(tài)。
如果是__weak和__block的組合呢?
當(dāng)超出作用域時(shí),__weak的對象也會(huì)置于nil。所以會(huì)得出結(jié)果:
array count = 0
array count = 0
array count = 0
如果不調(diào)用copy方法呢?
執(zhí)行該代碼后,程序會(huì)強(qiáng)制結(jié)束。因?yàn)橹挥姓{(diào)用_Block_copy才能持有截獲的附有__strong修飾符的對象類型的自動(dòng)變量值,否則即使截獲了對象,沒有retain,它也會(huì)隨著變量作用域的結(jié)束而被廢棄。
所以,Block中使用對象類型自動(dòng)變量時(shí),除以下情形外,需要調(diào)用Block的copy方法。
- Block作為函數(shù)返回值返回時(shí)
- 將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量
- 向方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時(shí)
三. Block的循環(huán)引用
如果在Block中使用__strong 修飾符的對象類型自動(dòng)變量,那么當(dāng)Block從棧復(fù)制到堆時(shí),該對象為Block所持有。這樣容易引起循環(huán)引用。
typedef void(^blk_t)(void);
@interface MyObject()
{
blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init{
self = [super init];
_blk = ^{NSLog(@"self = %@",self);};
return self;
}
- (void)dealloc{
NSLog(@"dealloc");
}
//在main函數(shù)創(chuàng)建MyObject
int main(){
id obj = [[MyObject alloc] init];
return 0;
}
MyObject類對象的成員變量_blk持有Block的強(qiáng)引用,即MyObject對象持有Block。由于Block是賦值給成員變量_blk,Block會(huì)由棧復(fù)制到堆,并持有所使用的self。所以造成循環(huán)引用。

那避免循環(huán)引用有哪些方法呢?
① 聲明附有__weak修飾符的變量,并將self賦值使用。
- (instancetype)init{
self = [super init];
id __weak tmp = self;
_blk = ^{NSLog(@"self = %@",tmp);};
return self;
}

②使用__block變量(可用于ARC無效時(shí),不過缺點(diǎn)是必須執(zhí)行Block,將臨時(shí)變量tmp置為nil)
- (instancetype)init{
self = [super init];
id __block tmp = self;
_blk = ^{
NSLog(@"self = %@",tmp);
tmp = nil;
};
return self;
}

還有一點(diǎn)需要注意的時(shí),__block說明符解決循環(huán)引用時(shí)的方式在ARC和MRC是不一樣的。MRC中,Block從棧復(fù)制到堆時(shí),Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣?dòng)變量,不會(huì)被retain,若沒有附有__block,會(huì)被retain。所以以下可以解決循環(huán)引用。
- (instancetype)init{
self = [super init];
id __block tmp = self;
_blk = ^{
NSLog(@"self = %@",tmp);
};
return self;
}
