2.1 Blcoks概要
2.1.1 什么是Blocks
Blocks是C語言的擴充功能——“帶有自動變量(即局部變量)的匿名函數(shù)”。
使用Blocks可以不聲明C++和Objective-C類,也沒有使用靜態(tài)變量、靜態(tài)全局變量或全局變量時的問題,僅用編程C語言函數(shù)的源代碼量即可使用帶有自動變量值的匿名函數(shù)。
2.2 Blocks模式
2.2.1 Block語法
-
Block常量表達式:
??:
^ int (int count){
return count +1;
};
-
省略返回值類型的Block常量表達式:
省略返回值類型時,如果表達式有return語句就返回該返回值的類型。如果表達式?jīng)]有return語句就返回void類型。如果表達式有多個return語句,所有的return語句返回值的類型必須相同。
??:
^ int (int count){
return count +1;
};
省略int類型返回值:
^ (int count){
return count +1;
};
??:
^ void (int count){
count +1;
NSLog(@"%@",count+1);
};
省略void類型:
^ (int count){
count +1;
NSLog(@"%@",count+1);
};
-
省略返回值類型和參數(shù)列表的Block常量表達式:
如果不使用參數(shù),參數(shù)列表也可省略。
??:
^ void (void){
NSLog(@"Hello world");
};
省略void類型:
^ {
NSLog(@"Hello world");
};
2.2.2 Block類型變量
在Block語法下,可將Block語法賦值給聲明為Block類型的變量中(即源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”)。
??:
int (^blk) (int);
“Block”即指源代碼中的Block語法,也指由Block語法所生成的值。
-
使用Block語法將Block賦值為Block類型變量。
??:
int (^blk) (int) = ^ int (int count){
return count +1;
};
-
由Block類型變量向Block類型變量賦值。
??:
int (^blk1) (int) = blk;
int (^blk2) (int);
blk2 = blk1;
-
Block類型變量作為函數(shù)參數(shù)傳遞。
??:
-(void)blockFunc:(int (^)(int))blk;
-
Block類型變量作為函數(shù)返回值返回。
??:
-(int)blockFunc1{
int (^blk) (int) = ^(int count){
return count +1;
};
return blk(1);
}
//等同于:
-(int)blockFunc1{
return ^(int count){
return count +1;
}(1);
}
-
Block類型變量作為函數(shù)參數(shù)和返回值時,可以通過typedef為Block類型提供別名,從而起到簡化塊類型變量名的作用。
??:
typedef int (^BLK) (int);//
-(void)blockFunc:(BLK)blk;//作為函數(shù)參數(shù)
-(int)blockFunc1{
BLK blk = ^(int count){
return count +1;
};
return blk(1);
}//作為返回值
2.2.3 截獲自動變量值
Blocks中,Block常量表達式會截獲所使用的自動變量的值(即保存該自動變量的瞬間值),從而在執(zhí)行塊時使用。
??:
{
int val = 10;
void (^blk)() = ^{
NSLog(@"%d",val);
};
val = 2;
blk();
}//輸出為10;不是2;
2.2.4 __block說明符()
使用附有 __block說明符的自動變量可在Block中賦值該變量稱為 __block 變量。__block說明符也被稱之為存儲類型修改符。
??:
{
__block int val = 10;
void (^blk)() = ^{
var = 1;
NSLog(@"%d",val);
};
val = 2;
blk();
}//輸出為1;不是2或者10;
2.2.5 截獲的自動變量
-
如果給Block中截獲的自動變量賦值,需要給截獲的自動變量附加__block說明符。
-
截獲Objective-C對象,調(diào)用變更該對象的方法并不會產(chǎn)生編譯錯誤,但是,向截獲的自動變量(即所截獲的Objective-C對象)賦值則會產(chǎn)生錯誤。
-
在使用C語言數(shù)組時,Block中的截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲,需要通過指針實現(xiàn)對C語言數(shù)組自動變量的截獲。
??:
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
blk();
2.3 Blocks的實現(xiàn)
2.3.1 Block的實質(zhì)
Block實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
將Objective-C的代碼轉(zhuǎn)化為C++的代碼來理解Block的實現(xiàn)。
Objective-C 轉(zhuǎn) C++的方法:
- 在OC源文件block.m寫好代碼。
- 打開終端,cd到block.m所在文件夾。
- 輸入clang -rewrite-objc block.m,就會在當前文件夾內(nèi)自動生成對應(yīng)的block.cpp文件。
Objective-C中Block的實現(xiàn):
int main()
{
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
return 0;
}
轉(zhuǎn)化為C++代碼:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags; // 標志
int Reserved; // 今后版本升級所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
};
// block結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// Block的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
// _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 將來被調(diào)用的block內(nèi)部的代碼:block值被轉(zhuǎn)換為C的函數(shù)代碼
// __ceself為指向Block值的變量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved; // 今后版本升級所需的區(qū)域
unsigned long Block_size; // Block的大小
} __mian_block_desc_0_DATA = { // 該結(jié)構(gòu)體實例的初始化部分
0,
sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實例)的大小進行初始化
};
// main函數(shù)
int main()
{
// 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)__main_block_impl_0
void (*blk)(void) =
(void (*)(void)) & __main_block_impl_0(
(void *)__main_block_func_0, &__mian_block_desc_0_DATA);
//調(diào)用block
((void (*)(struct __block_impl *))(
(struct __block_impl *)blk)->FuncPtr) ((struct __block_impl *)blk);
return 0;
}
其中,Objective-C 中Block值轉(zhuǎn)化而來的C++代碼為:
// 將來被調(diào)用的block內(nèi)部的代碼:block值被轉(zhuǎn)換為C的函數(shù)代碼
// __ceself為指向Block值的變量。那么,*__cself 就是是指向Block的值的指針,也就相當于是Block的值它自己(OC里的self)。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
可以看出,Block結(jié)構(gòu)體就是__main_block_impl_0結(jié)構(gòu)體。Block的值就是通過__main_block_impl_0構(gòu)造出來的。Block結(jié)構(gòu)體的聲明:
// block結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// Block的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
// _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其中,__main_block_impl_0結(jié)構(gòu)體有三個部分:
- 第一個是成員變量impl,它是實際的函數(shù)指針,它指向__main_block_func_0。impl結(jié)構(gòu)體的聲明:
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags; // 標志
int Reserved; // 今后版本升級所需的區(qū)域
void *FuncPtr; // 函數(shù)指針
};
- 第二個是成員變量是指向__main_block_desc_0結(jié)構(gòu)體的Desc指針,是用于描述當前這個block的附加信息。
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved; // 今后版本升級所需的區(qū)域
unsigned long Block_size; // Block的大小
} __mian_block_desc_0_DATA = { // 該結(jié)構(gòu)體實例的初始化部分
0,
sizeof(struct __main_block_impl_0) // 使用Block(即__main_block_impl_0結(jié)構(gòu)體實例)的大小進行初始化
};
- 第三個部分是__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù),__main_block_impl_0 就是該 block 的實現(xiàn)。
// Block的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
// _NSConcreteStackBlock用于初始化__block_impl結(jié)構(gòu)體的isa成員。(將Block指針賦值給Block的結(jié)構(gòu)體成員變量isa)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
在這個結(jié)構(gòu)體的構(gòu)造函數(shù)里:
- isa指針保持這所屬類的結(jié)構(gòu)體的實例的指針。
- __main_block_imlp_0結(jié)構(gòu)體就相當于Objective-C類對象的結(jié)構(gòu)體
- _NSConcreteStackBlock相當于Block的結(jié)構(gòu)體實例
也就是說Block實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
Objective-C類與對象的實質(zhì)是什么呢?
Objective-C類與對象的實質(zhì):
Objective-C中的對象就是一個結(jié)構(gòu)體,并且所有的對象都有一個相同的結(jié)構(gòu)體(即Class是類,id是對象)。而且每一個對象都有一個isa指針,這個isa指向生成該對象的類。
Objective-C類與對象的實現(xiàn)中最基本的結(jié)構(gòu)體為objc_object結(jié)構(gòu)體和objc_class結(jié)構(gòu)體。其中:
id類型是objc_object結(jié)構(gòu)體的指針類型。
typedef struct objc_object {
Class isa;
} *id;
- Class是objc_class結(jié)構(gòu)體的指針類型。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
} ;
通過一個簡單的MyObject類來說明Objective-C類與對象的實質(zhì):
??:
@interface MyObject : NSObject
{
int val0;
int val1;
}
基于objc_object結(jié)構(gòu)體,該類的對象的結(jié)構(gòu)體如下:
struct MyObject {
Class isa; // 成員變量isa持有該類的結(jié)構(gòu)體實例指針
int val0; // 原先MyObject類的實例變量val0和val1被直接聲明為成員變量
int val1;
}
MyObject類的實例變量val0和val1被直接聲明為對象的成員變量。“Objective-C中由類生成對象”意味著,像該結(jié)構(gòu)體這樣“生成由該類生成的對象的結(jié)構(gòu)體實例”。生成的各個對象(即由該類生成的對象的各個結(jié)構(gòu)體實例),通過成員變量isa保持該類的結(jié)構(gòu)體實例指針。
各類的結(jié)構(gòu)體是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體:
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)體實例以及NSMutableArray的class_t結(jié)構(gòu)體實例等,均生成并保持各個類的class_t結(jié)構(gòu)體實例。
該實例持有聲明的成員變量、方法的名稱、方法的實現(xiàn)(即函數(shù)指針)、屬性以及父類的指針,并被Objective-C運行時庫所使用。
2.3.2 截獲自動變量值
使用Block的時候,不僅可以使用其內(nèi)部的參數(shù),還可以使用Block外部的局部變量。而一旦在Block內(nèi)部使用了其外部變量,這些變量就會被Block保存。
Objective-C代碼:
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "var = %d\n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;
fmt = "These values were changed. var = %d\n";
blk();
return 0;
}
轉(zhuǎn)化而成的C++代碼:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; // Block語法表達式“使用的自動變量”被追加到該結(jié)構(gòu)體
int val;
// 構(gòu)造函數(shù)
__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;
}
};
// 靜態(tài)函數(shù) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
const char *fmt = __cself->fmt;
int val = __cself->val;
/*
理解:
1. __main_block_impl_0結(jié)構(gòu)體實例(即Block)所截獲的自動變量在Block語法表達式執(zhí)行之前就被聲明定義,所以,在Objective-C的源代碼中,執(zhí)行Block語法表達式時無需改動便可使用截獲的自動變量值。
2. "截獲自動變量值"意味著在執(zhí)行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結(jié)構(gòu)體實例(即Block自身)中。
3. Block不能直接使用“C語言數(shù)組類型的自動變量”,所以,截獲自動變量時,會將其值傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù)進行保存
*/
printf(fmt, val);
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __mian_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";
// 調(diào)用結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)初始化該結(jié)構(gòu)體實例
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, fmt, val);
return 0;
}
- 在初始化結(jié)構(gòu)體實例時,會根據(jù)傳遞給構(gòu)造函數(shù)的參數(shù)對由自動變量追加的成員變量進行初始化。即執(zhí)行Block語法使用的自動變量(即截獲的自動變量)fmt和val會初始化結(jié)構(gòu)體實例,被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中。
__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; //截獲的自動變量
__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;
}
};
注意1:block沒有使用的自動變量不會被追加,如dmy變量。
注意2: 在初始化block結(jié)構(gòu)體實例時,增加了被截獲的自動變量,block的體積會變大。
- 執(zhí)行Block語法使用的自動變量fmt和var都是從__cself里面獲取的,更說明了二者是屬于block的。而且從注釋來看(注釋是由clang自動生成的),這兩個變量是值傳遞,而不是指針傳遞,也就是說Block僅僅截獲自動變量的值,所以這就解釋了即使改變了外部的自動變量的值,也不會影響B(tài)lock內(nèi)部的值。
函數(shù)體的代碼如下:
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);
}
2.3.3 __block說明符
Block中所使用的被截獲的自動變量就如“帶有自動變量值的匿名函數(shù)”所說,僅截獲自動變量的值。Block中使用自動變量后,在Block結(jié)構(gòu)體實例中重寫該自動變量也不會改變原先截獲的自動變量。
為了在Block中保存值,有兩種方案:
- 改變存儲于特殊存儲區(qū)域的變量。
- 通過__block修飾符來改變。
1. 改變存儲于特殊存儲區(qū)域的變量
- 全局變量,可以直接訪問。
- 靜態(tài)全局變量,可以直接訪問。
- 靜態(tài)變量,直接指針引用。(不適用)
Objective-C具體的實現(xiàn)的代碼:
int global_val = 1;//全局變量
static int static_global_val = 2;//全局靜態(tài)變量
int main()
{
static int static_val = 3;//靜態(tài)變量
void (^blk)(void) = ^{
global_val *=1;
static_global_val *=2;
static_val *=3;
};
return 0;
}
轉(zhuǎn)換成C++的代碼:
int global_val = 1; //全局變量
static int static_global_val = 2; // 靜態(tài)全局變量
// 結(jié)構(gòu)體 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val; // 原先的源代碼中使用的靜態(tài)變量被追加為成員變量
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_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;
}
};
// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
int *static_val = __cself->static_val;
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
/*
1. 使用靜態(tài)變量static_val的指針對靜態(tài)變量進行訪問
2. 將靜態(tài)變量static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,這是超出作用域,使用靜態(tài)變量的最簡單方法
*/
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
// 主函數(shù),從這里開始閱讀源代碼
int main()
{
static int static_val = 3; // 靜態(tài)變量
// 調(diào)用__main_block_impl_0結(jié)構(gòu)體實例的構(gòu)造函數(shù),并將靜態(tài)變量static_val的指針作為參數(shù)傳遞給構(gòu)造函數(shù)
blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, &static_val);
return 0;
}
-
全局變量和全局靜態(tài)變量沒有被截獲到block里面,它們的訪問是不經(jīng)過block的(與__cself無關(guān)):
// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
int *static_val = __cself->static_val;
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
/*
1. 使用靜態(tài)變量static_val的指針對靜態(tài)變量進行訪問
2. 將靜態(tài)變量static_val的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,這是超出作用域,使用靜態(tài)變量的最簡單方法
*/
}
-
訪問靜態(tài)變量(static_val)時,將靜態(tài)變量的指針傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val; // 原先的源代碼中使用的靜態(tài)變量被追加為成員變量
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_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;
}
};
由上述可知, 超出作用域使用指針訪問靜態(tài)變量的這種方法并不適用于自動變量的訪問。
實際上,在由Block語法生成的值Block上,可以存有超過其變量作用域的被截獲對象的自動變量。變量作用域結(jié)束的同時,原來的自動變量被廢棄,因此Block中超過變量作用域而存在的變量,將不能通過指針訪問原來的自動變量。
2. 通過__block修飾符來改變。
__block說明符用于指定將變量值設(shè)置到哪個存儲區(qū)域中,也就是說,當自動變量加上__block說明符之后,會改變這個自動變量的存儲區(qū)域。
__block說明符用來指定Block中想變更值的自動變量,加上__block之后的變量稱之為__block變量
Objective-C中__block變量具體的實現(xiàn)的代碼:
int main()
{
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
轉(zhuǎn)換而成的C++代碼:
// 結(jié)構(gòu)體 __Block_byref_val_0
struct __Block_byref_val_0 {
// 成員變量
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; // 相當于原自動變量的成員變量
};
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; //“持有相當于原自動變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實例”被追加到成員變量中
// 構(gòu)造函數(shù)
__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;
}
};
// 靜態(tài)函數(shù) __main_block_func_0 (Block語法表達式發(fā)生的轉(zhuǎn)換)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
/*
1. Block的__main_block_impl_0結(jié)構(gòu)體實例持有指向“__block變量的__Block_byref_val_0結(jié)構(gòu)體實例”的指針(即__Block_byref_val_0 *val)
2. __Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding持有指向”該實例自身“的指針
3. 因此,通過__Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding可以訪問該結(jié)構(gòu)體實例的成員變量val
*/
}
// 靜態(tài)函數(shù) __main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src){
_Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}
// 靜態(tài)函數(shù) __main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0*src){
_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
// 主函數(shù)
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
/*
1. __block變量會變成__Block_byref_val_0結(jié)構(gòu)體類型的自動變量(即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實例)。
2. 該自動變量被初始化為10,這個值也出現(xiàn)在結(jié)構(gòu)體實例的初始化中,意味著該結(jié)構(gòu)體持有相當于原自動變量的成員變量。
*/
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
- 結(jié)構(gòu)體__main_block_impl_0里面增加了一個成員變量,它是一個結(jié)構(gòu)體指針,指向了__Block_byref_val_0結(jié)構(gòu)體的一個實例。
注意1: __Block_byref_val_0結(jié)構(gòu)體并不在__main_block_impl_0結(jié)構(gòu)體中,目的是為了使得多個Block中使用 __block變量。
注意2:__Block_byref_val_0結(jié)構(gòu)體這個結(jié)構(gòu)體是變量val在被__block修飾后生成的。
結(jié)構(gòu)體__main_block_impl_0的聲明:
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; //“持有相當于原自動變量的成員變量”的“__main_block_impl_0結(jié)構(gòu)體實例”被追加到成員變量中
// 構(gòu)造函數(shù)
__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;
}
};
- 結(jié)構(gòu)體__Block_byref_val_0兩個成員變量需要特別注意:
- val:保存了最初的val變量,也就是說原來單純的int類型的val變量被__block修飾后生成了一個結(jié)構(gòu)體。這個結(jié)構(gòu)體其中一個成員變量持有原來的val變量。
- __forwarding:通過__forwarding,可以實現(xiàn)無論__block變量配置在棧上還是堆上都能正確地訪問__block變量,也就是說__forwarding是指向自身的。
結(jié)構(gòu)體__Block_byref_val_0的聲明:
// 結(jié)構(gòu)體 __Block_byref_val_0
struct __Block_byref_val_0 {
// 成員變量
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; // 相當于原自動變量的成員變量
};
__forwarding成員變量的實現(xiàn) :
最初,block變量在棧上時,它的成員變量forwarding指向棧上的__block變量結(jié)構(gòu)體實例。
在__block被復(fù)制到堆上時,會將forwarding的值替換為堆上的目標block變量用結(jié)構(gòu)體實例的地址。而在堆上的目標block變量自己的forwarding的值就指向它自己。
2.3.4 Block存儲域
Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型的自動變量,__block變量轉(zhuǎn)換為 __block的結(jié)構(gòu)體類型的自動變量。
所謂結(jié)構(gòu)體類型的自動變量,即棧上生成的該結(jié)構(gòu)體的實例。而Block共有三種類型。
Block與__block變量的實質(zhì):
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
Block的類有三種:
注意:在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。
三種Block在內(nèi)存中的位置:
全局Block:_NSConcreteGlobalBlock
Block為_NSConcreteGlobalBlock類對象(即Block配置在程序的數(shù)據(jù)區(qū)域中)的情況有兩種:
-
記述全局變量的地方有Block語法時??:
void (^blk)(void) = ^{printf("Global Block\n");};
int main()
{
blk();
}
這里通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明是:
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;//全局
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Block結(jié)構(gòu)體構(gòu)造函數(shù)里面isa指針被賦予的是&_NSConcreteGlobalBlock,說明它是一個全局Block。
-
Block語法表達式中不使用“應(yīng)截獲的自動變量”時
??:
int(^block)(int count) = ^(int count) {
return count;
};
block(2);
這里通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明是:
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = & _NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Block結(jié)構(gòu)體構(gòu)造函數(shù)里面isa指針被賦予的是& _NSConcreteStackBlock
這里引用巧神的一段話:由于 clang 改寫的具體實現(xiàn)方式和 LLVM 不太一樣,并且這里沒有開啟 ARC。所以這里我們看到 isa 指向的還是_NSConcreteStackBlock。但在 LLVM 的實現(xiàn)中,開啟 ARC 時,block 應(yīng)該是 _NSConcreteGlobalBlock 類型
棧Block:_NSConcreteStackBlock
-
在生成Block以后,如果這個Block不是全局Block,那么它就是為_NSConcreteStackBlock對象。
-
配置在全局區(qū)的block,從變量作用域外也可以通過指針安全地使用。但是設(shè)置在棧上的block,如果其作用域結(jié)束,該block就被銷毀。
-
同樣的,由于__block變量也配置在棧上,如果其作用域結(jié)束,則該__block變量也會被銷毀。
堆Block:_NSConcreteMallocBlock
但是,如果Block變量和__block變量復(fù)制到了堆上以后,則不再會受到變量作用域結(jié)束的影響了,因為它變成了堆Block。
將棧block復(fù)制到堆以后,block結(jié)構(gòu)體的isa成員變量變成了_NSConcreteMallocBlock。
__block變量的結(jié)構(gòu)體成員變量__forwarding可以實現(xiàn)無論__block變量配置在棧上還是堆上時都能夠正確地訪問__block變量。圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
ARC有效時,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實現(xiàn):
-
block作為函數(shù)值返回的時候
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
該源代碼中的函數(shù)會返回配置在棧上的Block。即當程序執(zhí)行從該函數(shù)返回函數(shù)調(diào)用方時,變量作用域結(jié)束,因此棧上的Block也被廢棄。但該源代碼通過對應(yīng)ARC的編譯器可轉(zhuǎn)換如下:
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
/*
* ARC有效時,blk_t tmp 相當于blk_t __strong tmp.
* 將通過Block語法生成的Block(即配置在棧上的Block結(jié)構(gòu)體實例)
* 賦值給相當于Block類型的變量tmp
*/
tmp = objc_retainBlock(tmp);
/*
* objc_retainBlock實際上是_Block_copy函數(shù)
* 將棧上的Block復(fù)制到堆上
* 復(fù)制后,將堆上的地址作為指針賦值給變量tmp
*/
return objc_autoreleaseReturnValue(tmp);
/*
* 將堆上的Block作為Objective-C對象
* 注冊到autoreleasepool中,然后返回該對象
*/
}
-
部分情況下向方法或函數(shù)中傳遞block的時候
- Cocoa框架的方法而且方法名中含有usingBlock等時。
- Grand Central Dispatch 的API。
除了以上情況,需要我們手動復(fù)制block。
2.3.5 __block變量存儲域
-
任何一個block被復(fù)制到堆上時,__block變量也會一并從棧復(fù)制到堆上,并被該Block持有。
-
如果接著有其他Block被復(fù)制到堆上的話,被復(fù)制的Block會持有__block變量,并增加block的引用計數(shù)。
-
如果Block被廢棄,它所持有的__block也就被釋放(不再有block引用它)。
2.3.6 截獲對象
首先,我們來看一下生成并持有NSMutableArray類的對象的代碼??:
{
id array = [[NSMutableArray alloc] init];
}
變量作用域結(jié)束的同時,附有__strong修飾符的變量array被立即釋放并廢棄
而在block里截獲array對象的代碼??:
blk_t blk;
{
id array = [NSMutableArray new];
blk = [^(id object){
[array addObject:object];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
其輸出:
array count = 1
array count = 2
array count = 3
賦值給變量array的NSMutableArray類的對象在Block的執(zhí)行部分超出其變量作用域而存在。即array超過了其作用域存在。
通過clang轉(zhuǎn)換成的C++代碼中Block結(jié)構(gòu)體的聲明:
// 結(jié)構(gòu)體 __main_block_impl_0
struct __main_block_impl_0 {
// 成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
// 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong_array, int flags =0) : array(_array){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
被NSMutableArray類對象并被截獲的自動變量array,是附有__strong修飾符的成員變量。在Objective-C中,C語言結(jié)構(gòu)體不能含有附有__strong修飾符的變量。因為編譯器不知道何時進行C語言結(jié)構(gòu)體的初始化和廢棄操作,不能很好地管理內(nèi)存。
但是,Objective-C的運行時庫能準確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時機,因此Block的結(jié)構(gòu)體即時含有附有__stong修飾符或__weak修飾符的變量,也可以恰當?shù)剡M行初始化和廢棄。
在實現(xiàn)上是通過__main_block_copy_0函數(shù)和__main_block_dispose_0函數(shù)進行的:
// 靜態(tài)結(jié)構(gòu)體 __main_block_desc_0
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __mian_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
- __main_block_copy_0函數(shù)(copy函數(shù))和__main_block_dispose_0函數(shù)(dispose函數(shù))指針被賦值__main_block_desc_0結(jié)構(gòu)體成員變量copy和dispose中,但是在轉(zhuǎn)換后的源代碼中,這些函數(shù)包括使用指針全都沒有被調(diào)用。
- 而是,在Block從棧復(fù)制到堆時以及堆上的Block被廢棄時會調(diào)用這些函數(shù)。
調(diào)用copy函數(shù)和dispose函數(shù)的時機
2.3.7 __block變量和對象
__block可以指定任何類型的自動變量。下面指定用于賦值Objective-C對象的id類型自動變量:
__block id obj = [[NSObject alloc] init];
等同于:
// ARC有效時,id類型以及對象類型變量默認附加__strong修飾符。
__block id __strong obj = [[NSObject alloc] init];
由clang轉(zhuǎn)換成C++代碼:
/* __block變量的結(jié)構(gòu)體部分 */
// 結(jié)構(gòu)體 __Block_byref_obj_0
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose_)(void*);
__strong id obj; // __block變量被追加為成員變量
};
// 靜態(tài)函數(shù) __Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src){
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
// 靜態(tài)函數(shù) __Block_byref_id_object_dispose_131
static void __Block_byref_id_object_dispose_131(void *src){
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
/* __block變量聲明部分 */
__Block_byref_obj_0 obj = {
0,
&obj,
0x20000000,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
[[NSObject alloc] init]
};
-
在Block中使用“附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞俊钡那闆r下:
當Block從棧復(fù)制到堆時,使用_Block_object_copy函數(shù),持有Block截獲的對象。
當堆上的Block被廢棄時,使用_Block_object_dispose函數(shù),釋放Block截獲的對象。
-
在__block變量為“附有 __strong修飾符的id類型或?qū)ο箢愋妥詣幼兞俊钡那樾蜗聲l(fā)生同樣的過程。
當__block變量從棧復(fù)制到堆時,使用_Block_object_copy函數(shù),持有賦值給__block變量的對象。
當堆上的__block變量被廢棄時,使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對象。
由此可知:只要__block變量在堆上繼續(xù)存在,那么該對象就會繼續(xù)處于被持有的狀態(tài)。這與在Block中,對象賦值給“附有__strong修飾符的對象類型自動變量”相同。
- 對象賦值給“附有__weak修飾符的id類型或?qū)ο箢愋蚠_block自動變量”時,__block變量對該對象持有弱引用。此時nil賦值在自動變量上。
- 在使用附有__unsafe_unretained修飾符的變量時,注意不要通過懸掛指針訪問已被廢棄的對象,否則程序可能會崩潰!
- __autoreleasing修飾符與__block說明符同時使用會產(chǎn)生編譯錯誤。
2.3.8 Block循環(huán)引用
如果在Block內(nèi)部使用__strong修飾符的對象類型的自動變量,那么當Block從棧復(fù)制到堆的時候,該對象就會被Block所持有。
何時棧上的Block會復(fù)制到堆:
- 調(diào)用Block的copy實例方法時
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時
如果這個對象還同時持有Block的話,就容易發(fā)生循環(huán)引用。
- 使用Block類型成員變量和附有__strong修飾符的self出現(xiàn)循環(huán)引用
typedef void(^blk_t)(void);
@interface Person : NSObject
{
blk_t blk_;
}
@implementation Person
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"self = %@",self);
};
return self;
}
@end
Block blk_t持有self,而self也同時持有作為成員變量的blk_t
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
- Block中沒有使用self,但是截獲了self
@interface MyObject : NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@", obj_);};
return self;
}
Block語法中使用的obj_實際上截獲了self,而對編譯器來說,obj_只不過是對象的結(jié)構(gòu)體的成員變量。
blk_ = ^{NSLog(@"obj_ = %@", self->obj_);};
有兩種方法來解決Block循環(huán)引用:
- 使用__block變量避免循環(huán)引用
- 使用Block類型成員變量和附有__weak修飾符的對象(通常是self)避免循環(huán)引用
使用__block變量避免循環(huán)引用
typedeft void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
__block id blockSelf = self;
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil; // 記得清零
};
return self;
}
- (void)execBlock
{
blk_();
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
如果不調(diào)用execBlock實例方法(即不執(zhí)行賦值給成員變量blk_的Block),便會循環(huán)引用并引起內(nèi)存泄露。
使用__block變量不恰當會出現(xiàn)循環(huán)引用
在生成并持有對象的狀態(tài)下會引起以下循環(huán)引用
如果不執(zhí)行execBlock實例方法,就會持續(xù)該循環(huán)引用從而造成內(nèi)存泄露。
通過執(zhí)行execBlock實例方法,Block被執(zhí)行,nil被賦值在__block變量blockSelf中,__block變量blockSelf對MyObject類對象的強引用失效,從而避免了循環(huán)引用
圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》
使用__block變量避免循環(huán)引用的優(yōu)缺點
優(yōu)點:
- 通過__block變量可控制對象的持有期間
- 在不能使用__weak修飾符的環(huán)境中使用
缺點:
- 為避免循環(huán)引用必須執(zhí)行Block
使用Block類型成員變量和附有__weak修飾符的對象(通常是self)避免循環(huán)引用
- (id)init
{
self = [super init];
__weak __typeof(&*self)weakSelf = self;
blk_ = ^{NSLog(@"self = %@", weakSelf);};
return self;
}
若由于Block引發(fā)了循環(huán)引用時,在ARC有效時,使用__weak修飾符來避免Block中的循環(huán)引用。ARC無效時,__block說明符被用來避免Block中的循環(huán)引用。
總結(jié)
Blocks是“帶有自動變量(即局部變量)的匿名函數(shù)”。實質(zhì)是Objective-C對閉包的對象實現(xiàn),簡單說來,Block就是對象。
在Blocks中,Block常量表達式會截獲所使用的自動變量的值(即保存該自動變量的瞬間值)。附有 __block說明符的自動變量可在Block中賦值。
棧上的Block會復(fù)制到堆時,Block上的__block變量跟著復(fù)制到堆上。只要__block變量在堆上繼續(xù)存在,那么對象就會繼續(xù)處于被持有的狀態(tài)。
ARC有效時,大多數(shù)情況下Block從棧上復(fù)制到堆上的代碼由編譯器實現(xiàn):
- block作為函數(shù)值返回的時候
- 部分情況下向方法或函數(shù)中傳遞block的時候:
- Cocoa框架的方法而且方法名中含有usingBlock等時。
- Grand Central Dispatch 的API。