寫在前面
Block 是 C 語言的擴充功能。可以用一句話來表示 Block 的擴充功能:帶有自動變量(局部變量)的匿名函數(shù)。
基本概念
Block 定義
語法:^ 返回值類型 參數(shù)列表 表達式
^int(int count){
retrun count + 1;
}
跟普通函數(shù)就兩點不同
- 沒有函數(shù)名
- 帶有 ^
可以省略很多部分,省略返回類型:^ 參數(shù)列表 表達式
^(int count){
retrun count + 1;
}
當沒有參數(shù)的時候,參數(shù)列表也可以省略:^ 表達式
// 未省略參數(shù)列表
^(void){
NSLog(@"done");
}
// 省略參數(shù)列表
^{
NSLog(@"done");
}
Block 類型變量
先看一下 C 語言的函數(shù)指針
int func(int count) {
retrun count + 1;
}
int (*funcptr)(int) = &func;
而 Block 類型變量為:int (^blk)(int),僅僅是把 * 改成 ^ 。
int (^blk)(int) = ^(int count){
return count + 1;
};
Block 截獲外部變量和 __block 修飾符
int global_val = 1;
static int global_static_val = 1;
int main(int argc, const char * argv[]) {
int val = 1;
static int static_val = 1;
void(^blk)(void) = ^{
NSLog(@"global_val:%d", global_val);
NSLog(@"global_static_val:%d", global_static_val);
NSLog(@"val:%d", val);
NSLog(@"static_val:%d", static_val);
};
global_val++;
global_static_val++;
val++;
static_val++;
blk();
return 0;
}
/*
打印如下:
global_val:2
global_static_val:2
val:1
static_val:2
*/
通過上面例子知道,全局變量和局部靜態(tài)變量在 block 表達式中,可以直接修改的;
當我們需要修改自動變量值時,需要使用 __block 修飾,否則無法編譯(__block 只能修飾局部自動變量)
int main()
{
__block int val = 1;
void(^blk)(void) = ^{
val++;
NSLog(@"val:%d", val);
};
val++;
blk();
return 0;
}
/*
打印如下:
val:3
*/
底層實現(xiàn)
通過 clang -rewrite-objc main.m 解析下面源碼
// ARC 環(huán)境
int main(int argc, const char * argv[]) {
void(^blk)(void) = ^{
NSLog(@"done");
};
blk();
return 0;
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //指向 block 表達式的函數(shù)指針
};
// block 實現(xiàn)的結構體
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;
}
};
//block 表達式的實現(xiàn)部分
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_01adff_mi_0);
}
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 argc, const char * argv[]) {
// 生成 __main_block_impl_0 結構體實例 blk
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 通過結構體類的函數(shù)指針,調用block表達式部分
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
這里可以得到一個結論:Block 底層是一個結構體實現(xiàn)。
再看看 clang 截獲普通外部變量的結構
// ARC 環(huán)境
int global_val = 1;
static int global_static_val = 1;
int main(int argc, const char * argv[]) {
int val = 1;
static int static_val = 1;
void(^blk)(void) = ^{
NSLog(@"global_val:%d", global_val);
NSLog(@"global_static_val:%d", global_static_val);
NSLog(@"val:%d", val);
NSLog(@"static_val:%d", static_val);
};
blk();
return 0;
}
int global_val = 1;
static int global_static_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val; // 局部自動變量,值傳遞
int *static_val; // 局部靜態(tài)變量,指針傳遞
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int *_static_val, int flags=0) : val(_val), static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
int *static_val = __cself->static_val; // bound by copy
// 全局變量直接訪問
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_09c968_mi_0, global_val);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_09c968_mi_1, global_static_val);
// 局部變量,通過傳遞訪問
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_09c968_mi_2, val);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_09c968_mi_3, (*static_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 argc, const char * argv[]) {
int val = 3;
static int static_val = 4;
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
小結如下:
| 變量類型 | 訪問方式 | 是否捕獲到block內部 |
|---|---|---|
| 局部自動變量 | 值傳遞 | 是 |
| 局部靜態(tài)變量 | 指針傳遞 | 是 |
| 全局變量 | 直接訪問 | 否 |
繼續(xù)看 __block 修飾的自動變量會是怎么樣的
// ARC 環(huán)境
int main()
{
__block int val = 1;
void(^blk)(void) = ^{
val++;
NSLog(@"val:%d", val);
};
val++;
blk();
return 0;
}
// __block 修飾的變量 val,轉換為結構體
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)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6fxgzrp50fv6r72895k282k40000gn_T_main_bfcad8_mi_0, (val->__forwarding->val));
}
// 可以理解為 retain
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*/);
}
// 可以理解為 release
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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
(val.__forwarding->val)++;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們發(fā)現(xiàn)通過 __block 修飾的自定變量轉換為 __Block_byref_a_0 結構體,而且多了一個 __forwarding 指針。在分析 __forwarding 之前,先分析一下 _NSConcreteStackBlock,Block 結構體中都會有一個 isa 指針指向 _NSConcreteStackBlock ,了解 objc 底層就會知道,isa 一般都是指向對象的所屬類,也就是說 Blcok 也可以當做一個 objc 類理解。
在上面例子中出現(xiàn)的都是 _NSConcreteStackBlock,那么 _NSConcreteGlobalBlock 和 _NSConcreteMallocBlock 是在什么條件生成的?
_NSConcreteGlobalBlock
void(^blk)(void) = ^{
NSLog(@"done");
};
int main(int argc, const char * argv[]) {
blk();
return 0;
}
clang 之后
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;
}
};
還有一種情況是,只要不截獲自動變量就會分配到數(shù)據區(qū)域
int a = 1;
int main(int argc, const char * argv[]) {
void(^blk)(void) = ^{
NSLog(@"%d", a);
};
blk();
return 0;
}
雖然通過 clang 轉換為 _NSConcreteStackBlock,實際上卻不同,可以通過斷點打印驗證;
總結起來就是:如果 Block 表達式中沒有截獲自動變量,那么 Block 的內存會分配在數(shù)據區(qū)域。
_NSConcreteStackBlock 與 _NSConcreteMallocBlock
如果不是分配在數(shù)據區(qū)域的對象,那么就是分配在棧上;至于堆上的 Block,需要對 Block 對象調用 copy 方法,才能移到堆上,ARC 下系統(tǒng)會在合適的時機自動移到堆上。
接下來分析,堆和棧的區(qū)別?以及系統(tǒng)在哪些情況下會自動移動到堆上?
- 棧上的 Block:超過作用域就會釋放
- 堆上的 Block:通過引用計數(shù)管理
// MRC 環(huán)境下
+ (void)test
{
int a = 1;
void(^blk)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"blk:%@", blk);
NSLog(@"blk copy:%@", [blk copy]);
}
/*
打印如下:
blk:__NSStackBlock__
blk copy:__NSMallocBlock__
*/
小結如下:
| 類 | 內存區(qū)域 | 條件 |
|---|---|---|
_NSConcreteGlobalBlock(__NSGlobalBlock__) |
數(shù)據區(qū)域(.data區(qū)) | 表達式中沒有截獲自動變量 |
_NSConcreteStackBlock(__NSStackBlock__) |
棧區(qū) | 表達式中截獲自動變量 |
_NSConcreteMallocBlock(__NSMallocBlock__) |
堆區(qū) |
__NSStackBlock__調用了copy |
系統(tǒng)在哪些情況下會自動移動到堆上?這個需要分 MRC 和 ARC 的情況來考慮。
// ARC 環(huán)境
typedef int(^block)(int);
block getBlock(int rate)
{
return ^(int count) {
return rate * count;
};
}
在 ARC 下通過編譯器可轉換如下:
block getBlock(int rate)
{
// ARC 下會默認修飾符 __strong,block tmp = XXX; 相當于 __strong block tmp = XXX;
block tmp = &__getBlock_block_impl_0((void *)__getBlock_block_func_0, &__getBlock_block_desc_0_DATA), rate);
// 相當于普通 oc 對象的 objc_retain 函數(shù);內部會調用 _Block_copy(tmp);
tmp = objc_retainBlock(tmp);
// 把 tmp 注冊到 autoreleasepool 中
return objc_autoreleaseReturnValue(tmp);
}
_Block_copy 函數(shù)就是把 Block 從棧上復制到堆上。到這里就可以解釋為什么 MRC 下,block 經常需要手動調用 copy,屬性修飾符也一般都要使用 copy,比如:@property (nonatomic, copy) block blk;,主要目的是把 Block 從棧上拷貝到堆上。
ARC 自動 copy 到堆上的情況
- Block 作為函數(shù)返回值時
- 將 Block 賦值給 __strong 指針時
- Block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數(shù)時
- Block 作為 GCD API 的方法參數(shù)時
有了這些做基礎,接下來解釋 __block 修飾的變量,生成的結構中 __forwarding 的作用了。
先說結論:通過 __forwarding 指針訪問,保證每次都能訪問到合適的內存。
當 block 還在棧上時,__forwarding 指向的是棧上的結構體對象;

當 block 復制到堆上時,堆上的 __forwarding 指向堆上的結構體,棧上的 __forwarding 指向堆上的結構體

所以通過 __forwarding->val 訪問結構體中的值會永遠都是合適的。
總結:Block 本質就是一個結構體,并且可以截獲內部使用的自動變量作為結構體的成員變量。
幾個注意的地方
循環(huán)引用問題
如果在 Block 中使用附有 __strong 修飾符的自動變量,那么當 Block 從棧復制到堆時,該對象為 Block 所強引用,這樣就會引起循環(huán)引用。
先看一個經典例子
// ARC 環(huán)境
typedef void(^Block)(void);
@implementation ARCObject
{
Block _blk;
NSString *_str;
}
+ (void)test
{
ARCObject *obj = [ARCObject new];
obj->_str = @"string";
obj->_blk = ^{
NSLog(@"%@", obj->_str);
};
obj->_blk();
}
@end
clang 之后的結構體如下
struct __ARCObject__test_block_impl_0 {
struct __block_impl impl;
struct __ARCObject__test_block_desc_0* Desc;
ARCObject *obj; // 強引用
__ARCObject__test_block_impl_0(void *fp, struct __ARCObject__test_block_desc_0 *desc, ARCObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
再看另一個經典例子,只訪問了成員變量
// ARC 環(huán)境
typedef void(^Block)(void);
@implementation ARCObject
{
Block _blk;
NSString *_str;
}
- (void)test
{
_str = @"string";
_blk = ^{
NSLog(@"%@", _str);
};
_blk();
}
@end
clang 之后的結構體如下
struct __ARCObject__test_block_impl_0 {
struct __block_impl impl;
struct __ARCObject__test_block_desc_0* Desc;
ARCObject *self; // 強引用整個對象
__ARCObject__test_block_impl_0(void *fp, struct __ARCObject__test_block_desc_0 *desc, ARCObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我們發(fā)現(xiàn)當 Block 訪問的是對象的成員變量時,會把對象的指針傳給 Block。
避免循環(huán)引用使用 __weak 修飾就可以了,比如上面列子,因為 __weak 是運行時添加的,所以沒法 clang 出來。
// ARC 環(huán)境
typedef void(^Block)(void);
@implementation ARCObject
{
Block _blk;
NSString *_str;
}
- (void)test
{
_str = @"string";
__weak typeof(_str) weakStr = _str;
_blk = ^{
NSLog(@"%@", weakStr);
};
_blk();
}
@end
總結
現(xiàn)在就比較好理解:Block 是一個帶有自動變量(局部變量)的匿名函數(shù)。
- 通過一個結構體實現(xiàn) Block,然后把 Block 表達式中使用的局部變量(局部自動變量、局部靜態(tài)變量)設置為結構體的成員變量;
- Block 內存管理的方式,ARC 模式下,大部分情況編譯器會自動幫我們完成從??截惖蕉焉?,然后通過引用計數(shù)管理 Block 對象。以下為編譯器自動完成拷貝的情況:
- Block 作為函數(shù)返回值時
- 將 Block 賦值給 __strong 指針時
- Block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數(shù)時
- Block 作為 GCD API 的方法參數(shù)時