目錄
- 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 的描述對象,存儲(chǔ)著 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ī)制掩缓。
對于全局變量讹蘑,
不會(huì)捕獲
到 block 內(nèi)部则北,訪問方式為直接訪問
;對于 auto 類型的局部變量店印,
會(huì)捕獲
到 block 內(nèi)部,block 內(nèi)部會(huì)自動(dòng)生成一個(gè)成員變量倒慧,用來存儲(chǔ)這個(gè)變量的值按摘,訪問方式為值傳遞
;對于 static 類型的局部變量纫谅,
會(huì)捕獲
到 block 內(nèi)部炫贤,block 內(nèi)部會(huì)自動(dòng)生成一個(gè)成員變量,用來存儲(chǔ)這個(gè)變量的地址付秕,訪問方式為指針傳遞
兰珍;-
對于對象類型的局部變量,block 會(huì)
連同它的所有權(quán)修飾符一起捕獲
询吴。
3.1 auto 類型的局部變量
auto 自動(dòng)變量:我們定義出來的變量掠河,默認(rèn)都是 auto 類型亮元,只是省略了。
auto int age = 10唠摹;
auto 類型的局部變量會(huì)捕獲
到 block 內(nèi)部爆捞,訪問方式為值傳遞
。
通過 Clang 將以下代碼轉(zhuǎn)換為 C++ 代碼:
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
-
__main_block_impl_0
對象內(nèi)部會(huì)生成一個(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
變量的值,不會(huì)影響到 block 內(nèi)部的age
變量找默。
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 10
3.2 static 類型的局部變量
static 類型的局部變量會(huì)捕獲
到 block 內(nèi)部艇劫,訪問方式為指針傳遞
。
通過 Clang 將以下代碼轉(zhuǎn)換為 C++ 代碼:
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
-
__main_block_impl_0
對象內(nèi)部會(huì)生成一個(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
變量的值,會(huì)影響到 block 內(nèi)部的age
變量骡技。
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 20
3.3 全局變量
全局變量不會(huì)捕獲
到 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 類型的局部變量可能會(huì)銷毀,其內(nèi)存會(huì)消失温赔,block 將來執(zhí)行代碼的時(shí)候不可能再去訪問那塊內(nèi)存蛤奢,所以捕獲其值;
- static 變量會(huì)一直保存在內(nèi)存中, 所以捕獲其地址即可远剩。
3.4 對象類型的 auto 變量
當(dāng) block 內(nèi)部訪問了對象類型的 auto 變量時(shí):
- 如果 block 是在棧上扣溺,將不會(huì)對 auto 變量產(chǎn)生強(qiáng)引用
- 如果 block 被拷貝到堆上
① block 內(nèi)部的 desc 結(jié)構(gòu)體會(huì)新增兩個(gè)函數(shù):
?copy(__main_block_copy_0
,函數(shù)名命名規(guī)范同__main_block_impl_0
)
?dispose(__main_block_dispose_0
)
② 會(huì)調(diào)用 block 內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)
④_Block_object_assign
函數(shù)會(huì)根據(jù) auto 變量的修飾符(__strong
瓜晤、__weak
锥余、__unsafe_unretained
)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用 - 如果 block 從堆上移除
① 會(huì)調(diào)用 block 內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)
③_Block_object_dispose
函數(shù)會(huì)自動(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 被拷貝到堆上徐矩,會(huì)連同對象的所有權(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 變量和全局變量會(huì)一直在內(nèi)存中)
3.5.2 __block 修飾符
- __block 可以用于解決 block 內(nèi)部無法修改 auto 變量值的問題;
- __block 不能修飾全局變量刽漂、靜態(tài)變量演训;
- 編譯器會(huì)將 __block 變量包裝成一個(gè)對象(
struct __Block_byref_age_0
(byref:按地址傳遞)); - 加 __block 修飾不會(huì)修改變量的性質(zhì)贝咙,它還是 auto 變量样悟;
- 一般情況下,對被捕獲變量進(jìn)行賦值(賦值!=使用)操作需要添加 __block 修飾符。比如給數(shù)組添加或者刪除對象窟她,就不用加 __block 修飾陈症;
- 在 MRC 下使用 __block 修飾對象類型,在 block 內(nèi)部不會(huì)對該對象進(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++ 代碼。
- 編譯器會(huì)將 __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 變量,但還是有差異雅宾,下面會(huì)講到养涮。
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í),并不會(huì)對 __block 變量產(chǎn)生強(qiáng)引用
當(dāng) block 被 copy 到堆時(shí)
① block 內(nèi)部的 desc 結(jié)構(gòu)體會(huì)新增兩個(gè)函數(shù):
?copy(__main_block_copy_0
眉抬,函數(shù)名命名規(guī)范同__main_block_impl_0
)
?dispose(__main_block_dispose_0
)
② 會(huì)調(diào)用 block 內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)
④_Block_object_assign
函數(shù)會(huì)對 __block 變量形成強(qiáng)引用(retain)-
當(dāng) block 從堆中移除時(shí)
① 會(huì)調(diào)用 block 內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)
③_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的 __block 變量(release)
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 變量也會(huì) copy 到堆上去。為了防止 age 的值賦值給棧上的 __block 變量贤惯,就使用了
__forwarding
洼专; - 當(dāng) __block 變量在棧上的時(shí)候,__block 變量的結(jié)構(gòu)體中的
__forwarding
指針指向自己孵构,這樣通過__forwarding
取到結(jié)構(gòu)體中的 age 給它賦值沒有問題屁商; - 當(dāng) __block 變量 copy 到堆上后,棧上的
__forwarding
指針會(huì)指向 copy 到堆上的 _block 變量結(jié)構(gòu)體颈墅,而堆上的__forwarding
指向自己蜡镶;
這樣不管我們訪問的是棧上還是堆上的 __block 變量結(jié)構(gòu)體,只要是通過__forwarding
指針訪問恤筛,都是訪問到堆上的 __block 變量結(jié)構(gòu)體官还;給 age 賦值,就肯定會(huì)賦值給堆上的那個(gè) __block 變量中的 age毒坛。
3.5.5 對象類型的 auto 變量望伦、__block 變量內(nèi)存管理區(qū)別
- 當(dāng) block 在棧上時(shí)林说,對它們都不會(huì)產(chǎn)生強(qiáng)引用
- 當(dāng) block 拷貝到堆上時(shí),都會(huì)通過 copy 函數(shù)來處理它們
- 當(dāng) block 從堆上移除時(shí)屯伞,都會(huì)通過 dispose 函數(shù)來釋放它們
3.5.6 被 __block 修飾的對象類型
- 當(dāng) __block 變量在棧上時(shí)腿箩,不會(huì)對指向的對象產(chǎn)生強(qiáng)引用
- 當(dāng) __block 變量被 copy 到堆時(shí)
①__Block_byref_object_0
即 __block 變量內(nèi)部會(huì)新增兩個(gè)函數(shù):
?copy(__Block_byref_id_object_copy
)
?dispose(__Block_byref_id_object_dispose
)
② 會(huì)調(diào)用 __block 變量內(nèi)部的 copy 函數(shù)
③ copy 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)
④_Block_object_assign
函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃?code>__strong、__weak
劣摇、__unsafe_unretained
)做出相應(yīng)的操作珠移,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于 ARC 時(shí)會(huì) retain,MRC 時(shí)不會(huì) retain) - 如果 __block 變量從堆上移除
① 會(huì)調(diào)用 __block 變量內(nèi)部的 dispose 函數(shù)
② dispose 函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)
③_Block_object_dispose
函數(shù)會(huì)自動(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)部不會(huì)對該對象進(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)部不會(huì)對該對象進(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下會(huì)自動(dòng)copy诅需,從棧復(fù)制到堆漾唉,所以為__NSMallocBlock__類型
** 在MRC下為__NSStackBlock__類型,需要手動(dòng)調(diào)用copy方法才會(huì)變?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類型 | 副本源的配置存儲(chǔ)區(qū) | 復(fù)制效果 |
---|---|---|
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)段區(qū) | 什么也不做 |
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteMallocBlock | 堆 | 引用計(jì)數(shù)增加 |
__ NSStackBlock __ 存在的問題:
以下是在 MRC 環(huán)境下赵刑,block 類型為__NSStackBlock__
。
當(dāng) test() 函數(shù)執(zhí)行完畢场刑,棧上的東西可能會(huì)被銷毀般此,數(shù)據(jù)就會(huì)變成垃圾數(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)境下科乎,編譯器會(huì)根據(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
都會(huì)對 block 進(jìn)行強(qiáng)引用,都會(huì)自動(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 會(huì)產(chǎn)生循環(huán)引用悔常?
- ① 相互循環(huán)引用:如果當(dāng)前 block 對當(dāng)前對象的某一成員變量進(jìn)行捕獲的話影斑,可能會(huì)對它產(chǎn)生強(qiáng)引用。而當(dāng)前 block 又由于當(dāng)前對象對其有一個(gè)強(qiáng)引用机打,就產(chǎn)生了相互循環(huán)引用的問題矫户;
- ② 大環(huán)引用:我們?nèi)绻褂?code>__block的話,在 ARC 下可能會(huì)產(chǎn)生循環(huán)引用(MRC 則不會(huì))残邀,在 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
會(huì)產(chǎn)生懸垂指針。
- 用
__block
解決(必須要調(diào)用 block):
缺點(diǎn):必須要調(diào)用 block空免,而且 block 里要將指針置為 nil空另。如果一直不調(diào)用 block,對象就會(huì)一直保存在內(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)部不會(huì)對該對象進(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ì)在堆上框杜。
使用注意:循環(huán)引用問題浦楣。
Q:block在給 NSMutableArray 添加或移除對象,需不需要添加 __block咪辱?
不需要振劳。
Q:block 的變量捕獲機(jī)制
block 的變量捕獲機(jī)制,是為了保證 block 內(nèi)部能夠正常訪問外部的變量油狂。
- 對于全局變量历恐,
不會(huì)捕獲
到 block 內(nèi)部寸癌,訪問方式為直接訪問
; - 對于 auto 類型的局部變量弱贼,
會(huì)捕獲
到 block 內(nèi)部蒸苇,block 內(nèi)部會(huì)自動(dòng)生成一個(gè)成員變量,用來存儲(chǔ)這個(gè)變量的值吮旅,訪問方式為值傳遞
溪烤; - 對于 static 類型的局部變量,會(huì)捕獲到 block 內(nèi)部庇勃,block 內(nèi)部會(huì)自動(dòng)生成一個(gè)成員變量檬嘀,用來存儲(chǔ)這個(gè)變量的地址,訪問方式為
指針傳遞
责嚷; - 對于對象類型的局部變量鸳兽,block 會(huì)
連同它的所有權(quán)修飾符一起捕獲
。
Q:為什么局部變量需要捕獲再层,全局變量不用捕獲呢贸铜?
- 作用域的原因,全局變量哪里都可以直接訪問聂受,所以不用捕獲蒿秦;
- 局部變量,外部不能直接訪問蛋济,所以需要捕獲棍鳖;
- auto 類型的局部變量可能會(huì)銷毀,其內(nèi)存會(huì)消失碗旅,block 將來執(zhí)行代碼的時(shí)候不可能再去訪問那塊內(nèi)存渡处,所以捕獲其值;
- static 變量會(huì)一直保存在內(nèi)存中祟辟, 所以捕獲其地址即可医瘫。
Q:self 會(huì)不會(huì)捕獲到 block 內(nèi)部?
會(huì)捕獲旧困。
OC 方法都有兩個(gè)隱式參數(shù)醇份,方法調(diào)用者self
和方法名_cmd
。
參數(shù)也是一種局部變量吼具。
Q:_name 會(huì)不會(huì)捕獲到 block 內(nèi)部僚纷?
會(huì)捕獲。
不是將_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
會(huì)捕獲到 block 內(nèi)部。
Q:__ NSStackBlock __ 存在的問題:
如果沒有將 block 從棧上 copy 到堆上艇潭,那我們訪問棧上的 block 的話,可能會(huì)由于變量作用域結(jié)束導(dǎo)致棧上的 block 以及 __block 變量被銷毀戏蔑,而造成內(nèi)存崩潰蹋凝。或者數(shù)據(jù)可能會(huì)變成垃圾數(shù)據(jù)总棵,盡管將來 block 還能正常調(diào)用鳍寂,但是它捕獲的變量的值已經(jīng)錯(cuò)亂了。
解決辦法:將 block 的內(nèi)存放堆里情龄,意味著它就不會(huì)自動(dòng)銷毀迄汛,而是由我們程序員來決定什么時(shí)候銷毀它。
Q:默認(rèn)情況下 block 是不能修改外面的 auto 變量的骤视,解決辦法鞍爱?
- 變量用 static 修飾(原因:捕獲 static 類型的局部變量是指針傳遞,可以訪問到該變量的內(nèi)存地址)
- 全局變量
- __block(我們只希望臨時(shí)用一下這個(gè)變量臨時(shí)改一下而已专酗,而改為 static 變量和全局變量會(huì)一直在內(nèi)存中)
Q:__block 修飾符使用注意點(diǎn):
在 MRC 下使用 __block 修飾對象類型睹逃,在 block 內(nèi)部不會(huì)對該對象進(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 下,不會(huì)產(chǎn)生循環(huán)引用琼掠;
- 在 ARC 下拒垃,會(huì)產(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横堡,對象就會(huì)一直保存在內(nèi)存中埋市,造成內(nèi)存泄漏。
作者:師大小海騰
鏈接:http://www.reibang.com/p/f8a44c366f09
來源:簡書
著作權(quán)歸作者所有命贴。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)道宅,非商業(yè)轉(zhuǎn)載請注明出處。