目錄
- 1.Block 的基本使用
- 2.Block 的底層數(shù)據(jù)結(jié)構(gòu)
- 3.Block 的變量捕獲機(jī)制
3.1 auto 類型的局部變量
3.2 static 類型的局部變量
3.3 全局變量
3.4 對象類型的 auto 變量
3.5 __block 修飾的變量
?3.5.1 __block 作用
?3.5.2 __block 修飾符
?3.5.3 __block 的內(nèi)存管理
?3.5.4 __block 的 __forwarding 指針
?3.5.5 對象類型的 auto 變量、__block 變量內(nèi)存管理區(qū)別
?3.5.6 被 __block 修飾的對象類型- 4.Block 的類型
- 5.Block 的 copy
- 6.Block 的循環(huán)引用問題
6.1 ARC
6.2 MRC- 7.相關(guān)面試題
1.Block 的使用
Block 是什么?
代碼塊,封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的 OC 對象,
Block 的聲明
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:類型別名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 返回值類型(^block變量名)(參數(shù)1類型,參數(shù)2類型,...)
void(^block)(void);
Block 的定義
// ^返回值類型(參數(shù)1,參數(shù)2,...){};
// 1.無返回值,無參數(shù)
void(^block1)(void) = ^{
};
// 2.無返回值,有參數(shù)
void(^block2)(int) = ^(int a){
};
// 3.有返回值,無參數(shù)(不管有沒有返回值,定義的返回值類型都可以省略)
int(^block3)(void) = ^int{
return 3;
};
// 以上Block的定義也可以這樣寫:
int(^block4)(void) = ^{
return 3;
};
// 4.有返回值,有參數(shù)
int(^block5)(int) = ^int(int a){
return 3 * a;
};
Block 的調(diào)用
// 1.無返回值,無參數(shù)
block1();
// 2.有返回值,有參數(shù)
int a = block5(2);
使用示例
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
2.Block 的底層數(shù)據(jù)結(jié)構(gòu)
- Block 本質(zhì)上也是一個(gè) OC 對象,它內(nèi)部也有個(gè)
isa指針; - Block 是封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的 OC 對象;
- Block 的底層數(shù)據(jù)結(jié)構(gòu)如下圖所示:

通過 Clang 將以下 Block 代碼轉(zhuǎn)換為 C++ 代碼,來分析 Block 的底層實(shí)現(xiàn)。
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"調(diào)用了block");
};
block();
}
return 0;
}
- Block 底層數(shù)據(jù)結(jié)構(gòu)就是一個(gè)
__main_block_impl_0結(jié)構(gòu)體對象,其中有__block_impl和__main_block_desc_0兩個(gè)結(jié)構(gòu)體對象成員。
main:表示 block 所在的函數(shù)
block:表示這個(gè)一個(gè) block
impl:表示實(shí)現(xiàn)(implementation)
0:表示這是該函數(shù)中的第一個(gè) block -
__main_block_func_0結(jié)構(gòu)體封裝了 block 里的代碼; -
__block_impl結(jié)構(gòu)體才是真正定義 block 的結(jié)構(gòu),其中的FuncPtr指針指向__main_block_func_0; -
__main_block_desc_0是 block 的描述對象,存儲著 block 的內(nèi)存大小等; - 定義 block 的本質(zhì):
調(diào)用__main_block_impl_0()構(gòu)造函數(shù),并且給它傳了兩個(gè)參數(shù)__main_block_func_0和&__main_block_desc_0_DATA。拿到函數(shù)的返回值,再取返回值的地址&__main_block_impl_0,把這個(gè)地址賦值給 block 變量。 - 調(diào)用 block 的本質(zhì):
通過__main_block_impl_0中的__block_impl中的FuncPtr拿到函數(shù)地址,直接調(diào)用。
// main.cpp
struct __main_block_impl_0 {
struct __block_impl impl; // block的結(jié)構(gòu)體
struct __main_block_desc_0* Desc; // block的描述對象,描述block的大小等
/* 構(gòu)造函數(shù)
** 返回值:__main_block_impl_0 結(jié)構(gòu)體
** 參數(shù)一:__main_block_func_0 結(jié)構(gòu)體
** 參數(shù)二:__main_block_desc_0 結(jié)構(gòu)體的地址
** 參數(shù)三:flags 標(biāo)識位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在棧上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// __main_block_func_0 封裝了block里的代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}
struct __block_impl {
void *isa; // block的類型
int Flags; // 標(biāo)識位
int Reserved; //
void *FuncPtr; // block的執(zhí)行函數(shù)指針,指向__main_block_func_0
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block本質(zhì)結(jié)構(gòu)體所占內(nèi)存空間
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
** void(^block)(void) = ^{
NSLog(@"調(diào)用了block");
};
** 定義block的本質(zhì):
** 調(diào)用__main_block_impl_0()構(gòu)造函數(shù)
** 并且給它傳了兩個(gè)參數(shù) __main_block_func_0 和 &__main_block_desc_0_DATA
** __main_block_func_0 封裝了block里的代碼
** 拿到函數(shù)的返回值,再取返回值的地址 &__main_block_impl_0,
** 把這個(gè)地址賦值給 block
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
));
/*
** block();
** 調(diào)用block的本質(zhì):
** 通過 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函數(shù)地址,直接調(diào)用
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
3.Block 的變量捕獲機(jī)制
為了保證 block 內(nèi)部能夠正常訪問外部的變量,block 有個(gè)變量捕獲機(jī)制。
對于全局變量,
不會捕獲到 block 內(nèi)部,訪問方式為直接訪問;對于 auto 類型的局部變量,
會捕獲到 block 內(nèi)部,block 內(nèi)部會自動(dòng)生成一個(gè)成員變量,用來存儲這個(gè)變量的值,訪問方式為值傳遞;對于 static 類型的局部變量,
會捕獲到 block 內(nèi)部,block 內(nèi)部會自動(dòng)生成一個(gè)成員變量,用來存儲這個(gè)變量的地址,訪問方式為指針傳遞;-
對于對象類型的局部變量,block 會
連同它的所有權(quán)修飾符一起捕獲。image
3.1 auto 類型的局部變量
auto 自動(dòng)變量:我們定義出來的變量,默認(rèn)都是 auto 類型,只是省略了。
auto int age = 10;
auto 類型的局部變量會捕獲到 block 內(nèi)部,訪問方式為值傳遞。
通過 Clang 將以下代碼轉(zhuǎn)換為 C++ 代碼:
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
-
__main_block_impl_0對象內(nèi)部會生成一個(gè)相同的age變量; -
__main_block_impl_0()構(gòu)造函數(shù)多了個(gè)參數(shù),用來捕獲訪問的外面的age變量的值,將它賦值給__main_block_impl_0對象內(nèi)部的age變量。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}
......
int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是值傳遞,我們修改外部的age變量的值,不會影響到 block 內(nèi)部的age變量。
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 10
3.2 static 類型的局部變量
static 類型的局部變量會捕獲到 block 內(nèi)部,訪問方式為指針傳遞。
通過 Clang 將以下代碼轉(zhuǎn)換為 C++ 代碼:
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
-
__main_block_impl_0對象內(nèi)部會生成一個(gè)相同類型的age指針; -
__main_block_impl_0()構(gòu)造函數(shù)多了個(gè)參數(shù),用來捕獲訪問的外面的age變量的地址,將它賦值給__main_block_impl_0對象內(nèi)部的age指針。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_a4bc7d_mi_0,(*age));
}
......
static int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是指針傳遞,我們修改外部的age變量的值,會影響到 block 內(nèi)部的age變量。
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 20
3.3 全局變量
全局變量不會捕獲到 block 內(nèi)部,訪問方式為直接訪問。
通過 Clang 將以下代碼轉(zhuǎn)換為 C++ 代碼:
int _age = 10;
static int _height = 20;
......
void(^block)(void) = ^{
NSLog(@"%d,%d",_age,_height);
};
block();
-
__main_block_impl_0對象內(nèi)并沒有生成對應(yīng)的變量,也就是說全局變量沒有捕獲到 block 內(nèi)部,而是直接訪問。
int _age = 10;
static int _height = 20;
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_12efa5_mi_0,_age,_height);
}
......
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
為什么局部變量需要捕獲,全局變量不用捕獲呢?
- 作用域的原因,全局變量哪里都可以直接訪問,所以不用捕獲;
- 局部變量,外部不能直接訪問,所以需要捕獲;
- auto 類型的局部變量可能會銷毀,其內(nèi)存會消失,block 將來執(zhí)行代碼的時(shí)候不可能再去訪問那塊內(nèi)存,所以捕獲其值;
- static 變量會一直保存在內(nèi)存中, 所以捕獲其地址即可。
3.4 對象類型的 auto 變量
當(dāng) block 內(nèi)部訪問了對象類型的 auto 變量時(shí):
- 如果 block 是在棧上,將不會對 auto 變量產(chǎn)生強(qiáng)引用
- 如果 block 被拷貝到堆上
① block 內(nèi)部的 desc 結(jié)構(gòu)體會新增兩個(gè)函數(shù):
?copy(__main_block_copy_0,函數(shù)名命名規(guī)范同__main_block_impl_0)
?dispose(__main_block_dispose_0)
② 會調(diào)用 block 內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
④_Block_object_assign函數(shù)會根據(jù) auto 變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用 - 如果 block 從堆上移除
① 會調(diào)用 block 內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
③_Block_object_dispose函數(shù)會自動(dòng)釋放引用的 auto 變量(release)
| 函數(shù) | 調(diào)用時(shí)機(jī) |
|---|---|
| copy 函數(shù) | 棧上的 block 復(fù)制到堆時(shí) |
| dispose 函數(shù) | 堆上的 block 被廢棄時(shí) |
如下代碼,block 保存在堆中,當(dāng)執(zhí)行完作用域2的時(shí)候,Person 對象并沒有被釋放,而是在執(zhí)行完作用域1的時(shí)候釋放,說明 block 內(nèi)部對 Person 對象產(chǎn)生了強(qiáng)引用。
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { //作用域1
MyBlock block;
{ //作用域2
Person *p = [Person new];
p.name = @"zhangsan";
block = ^{
NSLog(@"%@",p.name);
};
}
NSLog(@"-----");
}
return 0;
}
// -----
// Person-dealloc
通過 Clang 將以上代碼轉(zhuǎn)換為 C++ 代碼:
// 弱引用需要運(yùn)行時(shí)的支持,所以需要加上 -fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
__main_block_impl_0中生成了一個(gè)Person *__strong p指針,指向外面的 person 對象,且是強(qiáng)引用。
typedef void(*MyBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong p; // 強(qiáng)引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__strong p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
{
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_0);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_2);
}
return 0;
}
添加了__weak修飾后,當(dāng)執(zhí)行完作用域2的時(shí)候,Person 對象就被被釋放了。
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { //作用域1
MyBlock block;
{ //作用域2
__weak Person *p = [Person new];
p.name = @"zhangsan";
block = ^{
NSLog(@"%@",p.name);
};
}
NSLog(@"-----");
}
return 0;
}
// Person-dealloc
// -----
同樣的,通過 Clang 將以上代碼轉(zhuǎn)換為 C++ 代碼。
__main_block_impl_0中生成了一個(gè)Person *__weak p指針,指向外面的 person 對象,且是弱引用。
說明當(dāng) block 內(nèi)部 訪問了對象類型的 auto 變量時(shí),如果 block 被拷貝到堆上,會連同對象的所有權(quán)修飾符一起捕獲。
......
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak p; //弱引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *__weak p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_c61841_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
......
3.5 __block 修飾的變量
3.5.1 __block 作用
默認(rèn)情況下 block 是不能修改外面的 auto 變量的,解決辦法?
- 變量用 static 修飾(原因:捕獲 static 類型的局部變量是指針傳遞,可以訪問到該變量的內(nèi)存地址)
- 全局變量
- __block(我們只希望臨時(shí)用一下這個(gè)變量臨時(shí)改一下而已,而改為 static 變量和全局變量會一直在內(nèi)存中)
3.5.2 __block 修飾符
- __block 可以用于解決 block 內(nèi)部無法修改 auto 變量值的問題;
- __block 不能修飾全局變量、靜態(tài)變量;
- 編譯器會將 __block 變量包裝成一個(gè)對象(
struct __Block_byref_age_0(byref:按地址傳遞)); - 加 __block 修飾不會修改變量的性質(zhì),它還是 auto 變量;
- 一般情況下,對被捕獲變量進(jìn)行賦值(賦值!=使用)操作需要添加 __block 修飾符。比如給數(shù)組添加或者刪除對象,就不用加 __block 修飾;
- 在 MRC 下使用 __block 修飾對象類型,在 block 內(nèi)部不會對該對象進(jìn)行 retain 操作,所以在 MRC 環(huán)境下可以通過 __block 解決循環(huán)引用的問題。
使用示例
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"block-%d",age);
};
block();
NSLog(@"%d",age);
// block-20
// 20
通過 Clang 將以上代碼轉(zhuǎn)換為 C++ 代碼。
- 編譯器會將 __block 修飾的變量包裝成一個(gè)
__Block_byref_age_0對象; - 以上
age = 20;的賦值過程為:通過 block 結(jié)構(gòu)體里的(__Block_byref_age_0)類型的 age 指針,找到__Block_byref_age_0結(jié)構(gòu)體的內(nèi)存(即被 __block 包裝成對象的內(nèi)存),把__Block_byref_age_0結(jié)構(gòu)體里的 age 變量的值改為20。 - 由于編譯器將 __block 變量包裝成了一個(gè)對象,所以它的內(nèi)存管理幾乎等同于訪問對象類型的 auto 變量,但還是有差異,下面會講到。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_1,(age.__forwarding->age));
}
return 0;
}
3.5.3 __block 的內(nèi)存管理
當(dāng) block 在棧上時(shí),并不會對 __block 變量產(chǎn)生強(qiáng)引用
當(dāng) block 被 copy 到堆時(shí)
① block 內(nèi)部的 desc 結(jié)構(gòu)體會新增兩個(gè)函數(shù):
?copy(__main_block_copy_0,函數(shù)名命名規(guī)范同__main_block_impl_0)
?dispose(__main_block_dispose_0)
② 會調(diào)用 block 內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
④_Block_object_assign函數(shù)會對 __block 變量形成強(qiáng)引用(retain)-
當(dāng) block 從堆中移除時(shí)
① 會調(diào)用 block 內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
③_Block_object_dispose函數(shù)會自動(dòng)釋放引用的 __block 變量(release)image
3.5.4 __block 的 __forwarding 指針
__block 的__forwarding指針存在的意義?
為什么要通過 age 結(jié)構(gòu)體里的__forwarding指針拿到 age 變量的值,而不直接 age 結(jié)構(gòu)體拿到 age 變量的值呢?
__block 的__forwarding是指向自己本身的指針,為了不論在任何內(nèi)存位置,都可以順利的訪問同一個(gè) __block 變量。
- block 對象 copy 到堆上時(shí),內(nèi)部的 __block 變量也會 copy 到堆上去。為了防止 age 的值賦值給棧上的 __block 變量,就使用了
__forwarding; - 當(dāng) __block 變量在棧上的時(shí)候,__block 變量的結(jié)構(gòu)體中的
__forwarding指針指向自己,這樣通過__forwarding取到結(jié)構(gòu)體中的 age 給它賦值沒有問題; - 當(dāng) __block 變量 copy 到堆上后,棧上的
__forwarding指針會指向 copy 到堆上的 _block 變量結(jié)構(gòu)體,而堆上的__forwarding指向自己;
這樣不管我們訪問的是棧上還是堆上的 __block 變量結(jié)構(gòu)體,只要是通過__forwarding指針訪問,都是訪問到堆上的 __block 變量結(jié)構(gòu)體;給 age 賦值,就肯定會賦值給堆上的那個(gè) __block 變量中的 age。

3.5.5 對象類型的 auto 變量、__block 變量內(nèi)存管理區(qū)別
- 當(dāng) block 在棧上時(shí),對它們都不會產(chǎn)生強(qiáng)引用
- 當(dāng) block 拷貝到堆上時(shí),都會通過 copy 函數(shù)來處理它們

- 當(dāng) block 從堆上移除時(shí),都會通過 dispose 函數(shù)來釋放它們

3.5.6 被 __block 修飾的對象類型
- 當(dāng) __block 變量在棧上時(shí),不會對指向的對象產(chǎn)生強(qiáng)引用
- 當(dāng) __block 變量被 copy 到堆時(shí)
①__Block_byref_object_0即 __block 變量內(nèi)部會新增兩個(gè)函數(shù):
?copy(__Block_byref_id_object_copy)
?dispose(__Block_byref_id_object_dispose)
② 會調(diào)用 __block 變量內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
④_Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃?code>__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于 ARC 時(shí)會 retain,MRC 時(shí)不會 retain) - 如果 __block 變量從堆上移除
① 會調(diào)用 __block 變量內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
③_Block_object_dispose函數(shù)會自動(dòng)釋放指向的對象(release)
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block NSObject *object = [[NSObject alloc] init];
void(^block)(void) = ^{
object = [[NSObject alloc] init];
};
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
struct __Block_byref_object_0 {
void *__isa;
__Block_byref_object_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // copy
void (*__Block_byref_id_object_dispose)(void*); // dispose
NSObject *__strong object;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_object_0 *object; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_object_0 *_object, int flags=0) : object(_object->__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_object_0 *object = __cself->object; // bound by ref
(object->__forwarding->object) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->object, (void*)src->object, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->object, 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(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
__attribute__((__blocks__(byref))) __Block_byref_object_0 object = {
(void*)0,
(__Block_byref_object_0 *)&object,
33554432,
sizeof(__Block_byref_object_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((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)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// __block 對象結(jié)構(gòu)體的地址+40個(gè)字節(jié),即為結(jié)構(gòu)體中 object 對象的地址
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
注意:在 MRC 下使用 __block 修飾對象類型,在 block 內(nèi)部不會對該對象進(jìn)行 retain 操作,所以在 MRC 環(huán)境下可以通過 __block 解決循環(huán)引用的問題。
示例(MRC)
// 對象類型的捕獲,連同所有權(quán)修飾符一起捕獲,所以 block 對 person 強(qiáng)引用
HTPerson *person = [[HTPerson alloc] init];
void(^block)(void) = [^{
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
// 0x10053da30
// -[HTPerson dealloc]
// __block 修飾的對象類型的捕獲,MRC 下在 block 內(nèi)部不會對該對象進(jìn)行 retain 操作
__block HTPerson *person = [[HTPerson alloc] init];
void(^block)(void) = [^{
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
// -[HTPerson dealloc]
// 0x1007337d0
4.Block 的類型
block 有 3 種類型,可以通過調(diào)用 class 方法或者 isa 指針 查看具體類型,最終都是繼承自 NSBlock 類型。
| block類型 | 描述 | 環(huán)境 |
|---|---|---|
| __ NSGlobalBlock __ | ||
| ( _NSConcreteGlobalBlock ) | 全局block,保存在數(shù)據(jù)段 | 沒有訪問auto變量 |
| __ NSStackBlock __ | ||
| ( _NSConcreteStackBlock ) | 棧block,保存在棧區(qū) | 訪問了auto變量 |
| __ NSMallocBlock __ | ||
| ( _NSConcreteMallocBlock ) | 堆block,保存在堆區(qū) | __ NSStackBlock __調(diào)用了copy |
打印各種 block 的類型,以及遍歷 block 的父類類型,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block1)(void) = ^{
NSLog(@"hello");
};
/* block2
** 在ARC下會自動(dòng)copy,從棧復(fù)制到堆,所以為__NSMallocBlock__類型
** 在MRC下為__NSStackBlock__類型,需要手動(dòng)調(diào)用copy方法才會變?yōu)開_NSMallocBlock__類型
** 同時(shí),在不需要該block的時(shí)候需要手動(dòng)調(diào)用release方法
*/
int age = 10;
void(^block2)(void) = ^{
NSLog(@"%d",age);
};
NSLog(@"%@,%@,%@", [block1 class], [block2 class], [^{
NSLog(@"%d",age);
} class]);
Class class = [block1 class];
while (class) {
NSLog(@"%@",class);
class = [class superclass];
}
}
return 0;
}
// __NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject
每一種類型的 block 調(diào)用 copy 后的結(jié)果如下所示:
| block類型 | 副本源的配置存儲區(qū) | 復(fù)制效果 |
|---|---|---|
| _NSConcreteGlobalBlock | 程序的數(shù)據(jù)段區(qū) | 什么也不做 |
| _NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
| _NSConcreteMallocBlock | 堆 | 引用計(jì)數(shù)增加 |
__ NSStackBlock __ 存在的問題:
以下是在 MRC 環(huán)境下,block 類型為__NSStackBlock__。
當(dāng) test() 函數(shù)執(zhí)行完畢,棧上的東西可能會被銷毀,數(shù)據(jù)就會變成垃圾數(shù)據(jù)。盡管 block 還能正常調(diào)用,但是輸出的 age 的值發(fā)生了錯(cuò)亂。
void (^block)(void);
void test()
{
// __NSStackBlock__
int age = 10;
block = ^{
NSLog(@"%d", age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
// 272632936
解決辦法:調(diào)用copy方法,將棧 block 復(fù)制到堆。
void (^block)(void);
void test()
{
// __NSMallocBlock__
int age = 10;
block = [^{
NSLog(@"%d", age);
} copy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
[block release];
}
return 0;
}
// 10
5.Block 的 copy
在 ARC 環(huán)境下,編譯器會根據(jù)情況自動(dòng)將棧上的 block 復(fù)制到堆上,比如以下幾種情況:
- 手動(dòng)調(diào)用 block 的 copy 方法時(shí);
- block 作為函數(shù)返回值時(shí)(Masonry 框架中用很多);
- 將 block 賦值給
__strong指針時(shí); - block 作為 Cocoa API 中方法名含有
usingBlock的方法參數(shù)時(shí); - block 作為 GCD API 的方法參數(shù)時(shí)。
block 作為屬性的寫法:
- ARC 下寫
strong或者copy都會對 block 進(jìn)行強(qiáng)引用,都會自動(dòng)將 block 從棧 copy 到堆上; - 建議都寫成
copy,這樣 MRC 和 ARC 下一致。
// MRC
@property (nonatomic, copy) void(^block)(void);
// ARC
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, strong) void(^block)(void);
6.Block 的循環(huán)引用問題
為什么 block 會產(chǎn)生循環(huán)引用?
- ① 相互循環(huán)引用:如果當(dāng)前 block 對當(dāng)前對象的某一成員變量進(jìn)行捕獲的話,可能會對它產(chǎn)生強(qiáng)引用。而當(dāng)前 block 又由于當(dāng)前對象對其有一個(gè)強(qiáng)引用,就產(chǎn)生了相互循環(huán)引用的問題;
- ② 大環(huán)引用:我們?nèi)绻褂?code>__block的話,在 ARC 下可能會產(chǎn)生循環(huán)引用(MRC 則不會),在 ARC 下可以通過斷環(huán)的方式去解除循環(huán)引用。但是有一個(gè)弊端,如果該 block 一直得不到調(diào)用,循環(huán)引用就一直存在。
6.1 ARC
- 用
__weak或者__unsafe_unretained解決:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};

注意:
__unsafe_unretained會產(chǎn)生懸垂指針。
- 用
__block解決(必須要調(diào)用 block):
缺點(diǎn):必須要調(diào)用 block,而且 block 里要將指針置為 nil。如果一直不調(diào)用 block,對象就會一直保存在內(nèi)存中,造成內(nèi)存泄漏。
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
weakSelf = nil;
};
self.block();

6.2 MRC
- 用
__unsafe_unretained解決:同 ARC - 用
__block解決(在 MRC 下使用 __block 修飾對象類型,在 block 內(nèi)部不會對該對象進(jìn)行 retain 操作,所以在 MRC 環(huán)境下可以通過 __block 解決循環(huán)引用的問題)
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
7.相關(guān)面試題
Q:block 的本質(zhì)是什么?
封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的 OC 對象。
Q:block 的屬性修飾詞為什么是 copy?使用 block 有哪些使用注意?
block 一旦沒有進(jìn)行 copy 操作,就不會在堆上。
使用注意:循環(huán)引用問題。
Q:block在給 NSMutableArray 添加或移除對象,需不需要添加 __block?
不需要。
Q:block 的變量捕獲機(jī)制
block 的變量捕獲機(jī)制,是為了保證 block 內(nèi)部能夠正常訪問外部的變量。
- 對于全局變量,
不會捕獲到 block 內(nèi)部,訪問方式為直接訪問; - 對于 auto 類型的局部變量,
會捕獲到 block 內(nèi)部,block 內(nèi)部會自動(dòng)生成一個(gè)成員變量,用來存儲這個(gè)變量的值,訪問方式為值傳遞; - 對于 static 類型的局部變量,會捕獲到 block 內(nèi)部,block 內(nèi)部會自動(dòng)生成一個(gè)成員變量,用來存儲這個(gè)變量的地址,訪問方式為
指針傳遞; - 對于對象類型的局部變量,block 會
連同它的所有權(quán)修飾符一起捕獲。
Q:為什么局部變量需要捕獲,全局變量不用捕獲呢?
- 作用域的原因,全局變量哪里都可以直接訪問,所以不用捕獲;
- 局部變量,外部不能直接訪問,所以需要捕獲;
- auto 類型的局部變量可能會銷毀,其內(nèi)存會消失,block 將來執(zhí)行代碼的時(shí)候不可能再去訪問那塊內(nèi)存,所以捕獲其值;
- static 變量會一直保存在內(nèi)存中, 所以捕獲其地址即可。
Q:self 會不會捕獲到 block 內(nèi)部?
會捕獲。
OC 方法都有兩個(gè)隱式參數(shù),方法調(diào)用者self和方法名_cmd。
參數(shù)也是一種局部變量。
Q:_name 會不會捕獲到 block 內(nèi)部?
會捕獲。
不是將_name變量進(jìn)行捕獲,而是直接將self捕獲到 block 內(nèi)部,因?yàn)?code>_name是 Person 類的成員變量,_name來自當(dāng)前的對象/方法調(diào)用者self(self->_name)。
如果使用self.name即調(diào)用self的getter方法,即給self對象發(fā)送一條消息,那還是要訪問到self。self是局部變量,不是全局變量,所以self會捕獲到 block 內(nèi)部。
Q:__ NSStackBlock __ 存在的問題:
如果沒有將 block 從棧上 copy 到堆上,那我們訪問棧上的 block 的話,可能會由于變量作用域結(jié)束導(dǎo)致棧上的 block 以及 __block 變量被銷毀,而造成內(nèi)存崩潰。或者數(shù)據(jù)可能會變成垃圾數(shù)據(jù),盡管將來 block 還能正常調(diào)用,但是它捕獲的變量的值已經(jīng)錯(cuò)亂了。
解決辦法:將 block 的內(nèi)存放堆里,意味著它就不會自動(dòng)銷毀,而是由我們程序員來決定什么時(shí)候銷毀它。
Q:默認(rèn)情況下 block 是不能修改外面的 auto 變量的,解決辦法?
- 變量用 static 修飾(原因:捕獲 static 類型的局部變量是指針傳遞,可以訪問到該變量的內(nèi)存地址)
- 全局變量
- __block(我們只希望臨時(shí)用一下這個(gè)變量臨時(shí)改一下而已,而改為 static 變量和全局變量會一直在內(nèi)存中)
Q:__block 修飾符使用注意點(diǎn):
在 MRC 下使用 __block 修飾對象類型,在 block 內(nèi)部不會對該對象進(jìn)行 retain 操作,所以在 MRC 環(huán)境下可以通過 __block 解決循環(huán)引用的問題。
Q:__block 的 __forwarding 指針存在的意義?為什么要通過 age 結(jié)構(gòu)體里的 __forwarding 指針拿到 age 變量的值,而不直接 age 結(jié)構(gòu)體拿到 age 變量的值呢?
見 3.5.4 __block 的 __forwarding 指針。
Q:為什么通過 __weak 去修飾成員變量或?qū)ο缶涂梢赃_(dá)到規(guī)避循環(huán)引用的目的呢?
block 對于對象類型的局部變量連同所有權(quán)修飾符一起截獲,所以如果我們在外部定義的對象是 __weak 所有權(quán)修飾符的,那么在 block 中所產(chǎn)生的結(jié)構(gòu)體里所持有的變量也是 __weak 類型的。
Q:解決在 block 內(nèi)部通過弱指針訪問對象成員時(shí)編譯器報(bào)錯(cuò)的問題:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%d",strongSelf->age);
};
Q:以下代碼的打印結(jié)果是?
__block int multiplier = 6;
int (^block)(int) = ^(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"%d",block(2));
// 8
Q:以下代碼有問題嗎?
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
};
- 在 MRC 下,不會產(chǎn)生循環(huán)引用;
- 在 ARC 下,會產(chǎn)生循環(huán)引用,導(dǎo)致內(nèi)存泄漏,解決方案如下。
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p",weakSelf);
weakSelf = nil;
};
self.block();
缺點(diǎn):必須要調(diào)用 block,而且 block 里要將指針置為 nil。如果一直不調(diào)用 block,對象就會一直保存在內(nèi)存中,造成內(nèi)存泄漏。
作者:師大小海騰
鏈接:http://www.itdecent.cn/p/f8a44c366f09
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

