Block是OSX Snow Leopard和iOS 4引入的C語言擴(kuò)充功能,是一個(gè)帶有自動(dòng)變量(局部變量)的匿名函數(shù),也被稱為閉包。
Block語法
Block的表達(dá)式語法:
^ 返回值類型 參數(shù)列表 表達(dá)式
^ int (int count) { return count + 1; }
// 可省略返回值類型,如果表達(dá)式中有返回值就使用返回值的類型
^(int count) { return count + 1; }
// 如果沒有參數(shù),參數(shù)列表也可以省略
^{ printf("hello Blocks\n"); }
Block 類型變量
Block類型變量與一般的C語言變量完全相同,可作為:自動(dòng)變量、函數(shù)參數(shù)、靜態(tài)變量、靜態(tài)全局變量、全局變量。
聲明Block類型變量的方式
- 使用Block語法將Block賦值為Block類型變量
返回值 (^變量名)(參數(shù)列表) = ^參數(shù)列表 表達(dá)式
int (^block)(int) = ^(int count) { return count + 1; }
- 使用typedef來聲明Block類型變量
// 定義Block類型
typedef 返回值 (^變量名)(參數(shù)列表)
typedef int (^block_t)(int count);
// 聲明block_t類型的Block變量
block_t block;
Block的實(shí)現(xiàn)與本質(zhì)
Blcok語法實(shí)際上是作為極普通的C語言源代碼來處理的,可以在shell中通過clang命令將OC文件轉(zhuǎn)換為源碼文件。
clang -rewrite-objc 文件名
將以下Block語法轉(zhuǎn)換為源代碼形式
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
printf("hello block!");
};
block();
}
return 0;
}
源碼為:
// Block的結(jié)構(gòu)體實(shí)例的信息
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// Block的結(jié)構(gòu)體實(shí)例
struct __main_block_impl_0 {
// 結(jié)構(gòu)體實(shí)例成員變量信息:結(jié)構(gòu)體實(shí)例類型、函數(shù)等
struct __block_impl impl;
// 結(jié)構(gòu)體實(shí)例信息:區(qū)域和大小
struct __main_block_desc_0* Desc;
// __main_block_impl_0結(jié)構(gòu)體的構(gòu)造方法
// 第一個(gè)參數(shù) __main_block_func_0 是block變量的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)指針
//
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
// 實(shí)例類型名
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
// block的表達(dá)式函數(shù)
impl.FuncPtr = fp;
Desc = desc;
}
};
/*
* __main_block_func_0函數(shù) 是的Block實(shí)例的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)
* __cself是指向Blcok對(duì)象自身的變量,就像Objective-C中的self
**/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello block!");
}
// 其描述的是 __main_block_impl_0結(jié)構(gòu)體實(shí)例的區(qū)域和大小
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)};
// main.m文件
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
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);
}
return 0;
}
跟著代碼執(zhí)行流程走,首先執(zhí)行
void (^block)(void) = ^{
printf("hello block!");
};
下面源代碼將在棧上__main_block_impl_0 結(jié)構(gòu)體實(shí)例的指針賦值給 __main_block_impl_0 結(jié)構(gòu)體指針類型的變量block。
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 去掉類型轉(zhuǎn)換
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *block = &tmp
__main_block_impl_0 構(gòu)造函數(shù)的第一個(gè)參數(shù) __main_block_func_0 是block變量的表達(dá)式部分轉(zhuǎn)換的C語言函數(shù)指針,第二個(gè)參數(shù) __main_block_desc_0_DATA中存儲(chǔ)著 __main_block_impl_0 結(jié)構(gòu)體實(shí)例大小。
聲明好了block變量,就到了下一步,使用block:block()
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 去掉類型轉(zhuǎn)換
(*block->FuncPtr)(block);
此步驟就是簡單的使用函數(shù)指針調(diào)用函數(shù),所調(diào)用的函數(shù)就是上面源碼的 __main_block_func_0 函數(shù)。
總結(jié):由上可知Block實(shí)質(zhì)就是Objective-C的對(duì)象。
Block捕獲自動(dòng)變量
int main () {
int val = 10;
void (^block)(void) = ^{
printf("val = %d", val);
};
val = 1;
block();
}
上面的示例的結(jié)果是:val = 10,由于Block捕獲的是val的值,所以外部val變量的改動(dòng)并不會(huì)影響到Block中的val變量,但是,如果我們?cè)噲D在Block中改變val的值,由于Blcok捕獲的是val變量的值而不是地址,所以在Block中無法修改自動(dòng)變量val的值,因此編譯器會(huì)報(bào)錯(cuò),這一點(diǎn)在下節(jié)的“Block 是如何捕獲自動(dòng)變量”可明白。
int val = 10;
void (^block)(void) = ^{ val = 1; };
如果我們只使用值的話就不會(huì)有任何問題
int val = 10;
void (^block)(void) = ^{ printf("val = %d", val); };
id multArray = [NSMutableArray alloc] init];
void (^block)(void) = ^{
[multArray addObject:@"use is ok"];
}
當(dāng)然也有特殊情況,當(dāng)我們?cè)贐lock中只使用C語言的字符串字面變量數(shù)組時(shí),由于Block捕獲自動(dòng)變量的方法沒有實(shí)現(xiàn)對(duì)C語言數(shù)組的截獲,所以在Block中使用C語言的字符串字面變量數(shù)組會(huì)出錯(cuò)。
const char text[] = "hello";
void (^block)(void) = ^{
printf("%c\n",text[2]);
}
// 該用此做法就能解決問題
const char text = "hello";
void (^block)(void) = ^{
printf("%c\n",text[2]);
}
Block 是如何捕獲自動(dòng)變量
將捕獲自動(dòng)變量的代碼轉(zhuǎn)換為源代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
int val = 10;
void (^block)(void) = ^{
printf("val = %d", val);
};
block();
}
return 0;
}
源碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_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
printf("val = %d", 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int val = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
從上面的代碼可知道,Block將在表達(dá)式中用到的val自動(dòng)變量作為成員變量追加到 __main_block_impl_0 結(jié)構(gòu)體中
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
};
跟著程序執(zhí)行流程
int val = 10;
void (^block)(void) = ^{
printf("val = %d", val);
};
// 源碼:
int val = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
定義 val變量并賦予初始值10,將val值通過 __main_block_impl_0 的構(gòu)造方法對(duì)__main_block_impl_0追加的val成員變量進(jìn)行初始化。
// __main_block_impl_0 初始化
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = __main_block_desc_0_DATA;
val = 10;
使用block:block(),也就是執(zhí)行
^{ printf("val = %d", val); }
源碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("val = %d", val);
}
在執(zhí)行block的表達(dá)式時(shí),用的val自動(dòng)變量,是結(jié)構(gòu)體追加的val自動(dòng)變量,所以Block捕獲自動(dòng)變量并不是真的將變量直接作為成員變量,而是在結(jié)構(gòu)體中追加一個(gè)名字相同的成員變量,在構(gòu)造方法中將要捕獲的自動(dòng)變量值對(duì)追加的成員變量初始化。
__block 說明符
在此之前試圖在block表達(dá)式中改變捕獲的自動(dòng)變量的值時(shí)編譯器會(huì)報(bào)錯(cuò),解決方案有:
- 用__block 說明符修飾自動(dòng)變量
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_val = 3;
__block int block_val = 4;
void (^block)(void) = ^{
global_val = 2;
static_global_val = 3;
static_val = 4;
block_val = 5;
};
block();
printf("global_val: %d \n", global_val);
printf("static_global_val: %d \n", static_global_val);
printf("static_val: %d \n", static_val);
printf("block_val: %d \n", block_val);
}
return 0;
}
// 結(jié)果:
global_val: 2
static_global_val: 3
static_val: 4
block_val: 5
轉(zhuǎn)換的源碼為:
int global_val = 1;
static int static_global_val = 2;
struct __Block_byref_block_val_0 {
void *__isa;
__Block_byref_block_val_0 *__forwarding;
int __flags;
int __size;
int block_val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__Block_byref_block_val_0 *block_val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, __Block_byref_block_val_0 *_block_val, int flags=0) : static_val(_static_val), block_val(_block_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_block_val_0 *block_val = __cself->block_val; // bound by ref
int *static_val = __cself->static_val; // bound by copy
global_val = 2;
static_global_val = 3;
(*static_val) = 4;
(block_val->__forwarding->block_val) = 5;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_val, (void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_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(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int static_val = 3;
__attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,(__Block_byref_block_val_0 *)&block_val, 0, sizeof(__Block_byref_block_val_0), 4};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val, (__Block_byref_block_val_0 *)&block_val, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
printf("global_val: %d \n", global_val);
printf("static_global_val: %d \n", static_global_val);
printf("static_val: %d \n", static_val);
printf("block_val: %d \n", (block_val.__forwarding->block_val));
}
return 0;
}
以下是對(duì)四種變量改變值的表達(dá)式函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_block_val_0 *block_val = __cself->block_val; // bound by ref
int *static_val = __cself->static_val; // bound by copy
global_val = 2;
static_global_val = 3;
(*static_val) = 4;
(block_val->__forwarding->block_val) = 5;
}
Block僅捕獲了 block_val修飾的變量和staic_val,全局變量沒有捕獲,原因是block_val、staic_val作為局部變量,其作用域僅在 main 方法中,當(dāng)離開作用域時(shí)變量會(huì)被廢棄,所以Block需捕獲block_val、staic_val變量以保證在表達(dá)式函數(shù)的使用,然而全局變量的作用域很廣,所以Block無需捕獲,所以看出Block只對(duì)需要捕獲的變量進(jìn)行捕獲。
對(duì)于全局變量能在Block中修改值,如上面說的,它作用域很廣,所以在Block表達(dá)式函數(shù)結(jié)束后也能保存修改的值。靜態(tài)變量static_val是將其指針傳遞給 __main_block_impl_0 結(jié)構(gòu)體的構(gòu)造函數(shù)并保存,也就是說Block捕獲的并不是 static_val的值,而是其指針(即內(nèi)存地址),所以static_val能夠在超出作用域之外使用,在Block結(jié)束后也能保存修改后的值。
最后就是block_val變量了,block_val 是使用__block 說明符修飾的變量,“__block 說明符”也被稱為“__block 存儲(chǔ)域類說明符”,block_val在添加上“__block 說明符”后,源碼變換如下
__block int block_val = 4;
// 轉(zhuǎn)換后的源碼:
__attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,(__Block_byref_block_val_0 *)&block_val, 0, sizeof(__Block_byref_block_val_0), 4};
//去掉類型轉(zhuǎn)換后:
__Block_byref_block_val_0 block_val = { 0, &block_val, 0, sizeof(__Block_byref_block_val_0), 4};
// __Block_byref_block_val_0 結(jié)構(gòu)體
struct __Block_byref_block_val_0 {
void *__isa;
__Block_byref_block_val_0 *__forwarding;
int __flags;
int __size;
int block_val;
};
在添加上“__block 說明符”后,block_val變成了 __Block_byref_block_val_0 結(jié)構(gòu)體類型的自動(dòng)變量,并且其結(jié)構(gòu)體中含有一個(gè)相當(dāng)于原自動(dòng)變量block_val的成員變量,當(dāng)對(duì)block_val變量賦值時(shí)
__Block_byref_block_val_0 *block_val = __cself->block_val;
(block_val->__forwarding->block_val) = 5;
Blcok的 __main_block_impl_0 結(jié)構(gòu)體實(shí)例持有指向 block_val變量的__Block_byref_block_val_0 結(jié)構(gòu)體實(shí)例的指針,而__Block_byref_block_val_0 結(jié)構(gòu)體實(shí)例的成員變量 __forwarding 持有永遠(yuǎn)指向自身的指針,可以通過 __forwarding來訪問成員變量 block_val,因此在Block結(jié)束后,block_val變量能保存改動(dòng)后的值。這里還有一個(gè)疑問:block_val超出了block變量的作用域并且它是配置在棧上,為什么還能訪問?這個(gè)問題在下節(jié)“Block變量和__block變量存儲(chǔ)域”可以明白。
總結(jié):要在Blcok中改變被捕獲的自動(dòng)變量的值的方式有:
- 以指針(內(nèi)存地址)形式被Block捕獲,Block保存其指針后,對(duì)于內(nèi)容的更改便不會(huì)丟失
- 改變自動(dòng)變量的存儲(chǔ)方式,如__block, __Block_byref_block_val_0結(jié)構(gòu)體實(shí)例中擁有相當(dāng)于原自動(dòng)變量的成員變量并擁有永遠(yuǎn)指向自己的指針的成員變量__forwarding,不管變量在棧上還是堆上都能訪問。
Block變量和__block變量存儲(chǔ)域
上面所述可知,Block類型變量轉(zhuǎn)換為 __main_block_impl_0 結(jié)構(gòu)體類型變量,__block 變量轉(zhuǎn)換為 __Block_byref_block_val_0結(jié)構(gòu)體類型變量,所謂的結(jié)構(gòu)體類型變量即棧上生成的結(jié)構(gòu)體實(shí)例。
Block實(shí)質(zhì)就是Objective-C對(duì)象,它有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock。
- _NSConcreteStackBlock:
它是在棧上的Block類型,只用到外部局部變量、成員屬性變量,沒有強(qiáng)指針引用,一旦脫離作用域時(shí)就會(huì)被銷毀。 - _NSConcreteGlobalBlock:
它是在數(shù)據(jù)區(qū)域(.data區(qū))的Block類型,沒有用到外界變量或只用到全局變量的block,只有在應(yīng)用程序結(jié)束才會(huì)被銷毀。 - _NSConcreteMallocBlock
它是在堆(內(nèi)存塊)里的Block類型,有強(qiáng)指針引用或copy修飾的成員屬性引用,沒有強(qiáng)指針引用即銷毀。
_NSConcreteGlobalBlock 類型Block變量在超出作用域也能通過指針訪問,而_NSConcreteStackBlock類型Block在作用域結(jié)束后就會(huì)被廢棄,同樣的 __block 類型變量也是如此,為解決這個(gè)問題,Block語法提供了將Block 和 __block變量從棧上復(fù)制到堆上,這樣在Block變量作用域結(jié)束后,堆上的Block還能繼續(xù)存在。復(fù)制在堆上的Block會(huì)將 _NSConcreteMallocBlock 類名寫入Block 結(jié)構(gòu)體實(shí)例 __main_block_impl_0 中的成員變量 ipml 的isa變量中。
ipml.isa = &_NSConcreteMallocBlock;
而 __block 結(jié)構(gòu)體實(shí)例的成員變量 __forwarding 擁有永遠(yuǎn)指向自身的指針, 可以實(shí)現(xiàn)無論 __block 變量配置在棧上還是堆上也可以訪問 __block變量,因此即使__block變量在Block變量的作用域之外也能訪問__block變量并保存更改后的值。
Block 的copy方法和dispose方法
這是在MRC環(huán)境下的代碼示例
typedef void (^block_t)(id);
int main(int argc, const char * argv[]) {
@autoreleasepool {
id array = [[NSMutableArray alloc] init];
block_t block = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy];
block([[NSObject alloc] init]);
block([[NSObject alloc] init]);
block([[NSObject alloc] init]);
}
return 0;
}
// clang后的源碼:
typedef void (*block_t)(id);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2k_lvtjwq3x1h746y288_mm11200000gn_T_main_580dfd_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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;
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
block_t block = (block_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)block)->FuncPtr)((__block_impl *)block, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
}
return 0;
}
上面代碼中 Block捕獲了一個(gè) __strong 類型的變量 array,雖然源碼中沒有標(biāo)識(shí)。在Objective-C中,C語言結(jié)構(gòu)體不能含有 __strong 類型的變量,因?yàn)樵贐lock從棧上復(fù)制到堆以及堆上的Block廢棄時(shí),編譯器不知道何時(shí)進(jìn)行C語言結(jié)構(gòu)體的初始化和廢棄操作,但是由于 Objective-C的運(yùn)行庫能夠準(zhǔn)確把握Block從棧上復(fù)制到堆以及堆上的Block廢棄的時(shí)機(jī),因此在 __main_block_desc_0增加了 copy、dispose成員變量并賦予__main_block_copy_0、__main_block_dispose_0函數(shù),通過這個(gè)兩個(gè)函數(shù)管理此類變量的復(fù)制和廢棄。
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};
// BLOCK_FIELD_IS_OBJECT 和 BLOCK_FIELD_IS_BYREY 用于分辨copy和dispose函數(shù)的對(duì)象類型是對(duì)象還是 __block變量。
// 在Block 從棧上復(fù)制到堆上時(shí),會(huì)調(diào)用__main_block_copy_0 將 __strong 類型變量跟隨Block復(fù)制到堆上
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// 當(dāng)堆上Block 廢棄時(shí),__main_block_dispose_0 將 __strong 類型變量廢棄
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
在ARC下,編譯器會(huì)適當(dāng)?shù)剡M(jìn)行判斷去自動(dòng)執(zhí)行copy,而在MRC下需要自己手動(dòng)copy,看下面在ARC環(huán)境下的一個(gè)返回Block的函數(shù):
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count) { return rate *count; };
};
ARC編譯器轉(zhuǎn)換簡約后:
blk_t func(int race) {
blk_t tmp = &__func_block_impl_0(__func_block_func0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
第一步,通過Block結(jié)構(gòu)體構(gòu)造函數(shù)生成配置在棧上的結(jié)構(gòu)體實(shí)例并將其賦給Block 變量 tmp。第二步,objc_retainBlock()函數(shù)實(shí)質(zhì)是_Block_copy函數(shù)(由objc4 運(yùn)行庫可知),將棧上的Block變量tmp復(fù)制到堆上。第三步,將堆上的Block變量tmp作為Objective-C對(duì)象注冊(cè)到自動(dòng)釋放池中,然后返回改對(duì)象。雖說ARC下,編譯器會(huì)適當(dāng)?shù)剡M(jìn)行判斷去自動(dòng)生成“將Block 變量從棧上復(fù)制到堆上”的代碼,但是在此之外的情況,我們需要自己手動(dòng)copy,比如 alloc/new/copy/mutableCopy等方法其實(shí)都是將對(duì)象從棧上復(fù)制到堆上的操作。在ARC中有以下情況需要手動(dòng)copy:
- 向方法或者函數(shù)的參數(shù)中傳遞Block之前時(shí),當(dāng)然如果方法或或者函數(shù)中有對(duì)Block進(jìn)行復(fù)制的操作,那便不需要。
- NSArray類的 initWithObjects 實(shí)例方法
在ARC中有以下情況不需要手動(dòng)copy:
- Block作為函數(shù)值返回時(shí)
- 將 Block賦值給 使用 __strong 修飾符的id類型或 Block類型變量
- Coccoa 框架的方法且方法名中含有 usingBlock 中使用時(shí)。
- Grand Central Dispatch 的API中
對(duì)于不同類型的Block進(jìn)行copy操作后的情況
Block 的類 副本源的配置存儲(chǔ)域 復(fù)制結(jié)果
_NSConcreteStackBlock 棧 從棧復(fù)制到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么也不做
_NSConcreteMallocBlock 堆 引用計(jì)數(shù)增加
不管Block配置在何處,用copy方法都不會(huì)引起任何問題,因此在不確定的時(shí)候可以調(diào)用copy方法,但是需注意從棧上復(fù)制到堆上十分消耗CPU資源。
以上只對(duì)Block進(jìn)行了說明,那么對(duì)于__block 變量又是怎么處理的?
當(dāng)Block從棧上復(fù)制到堆上時(shí),其擁有的所有__block 變量也會(huì)隨Block全部被復(fù)制到堆上。棧上的__block變量結(jié)構(gòu)體中的成員變量 __forwarding的值會(huì)被替換成堆上的__block變量結(jié)構(gòu)體的地址,堆上的__block變量結(jié)構(gòu)體中的成員變量 __forwarding依然指向自身,因此,不管是在棧上我們都能通過__forwarding 訪問同一個(gè)__block 變量。
Block 循環(huán)引用
Block 是如何引起循環(huán)引用的
#import <Foundation/Foundation.h>
typedef void (^printBlock_t)(void);
@interface TestCircleRetain : NSObject {
printBlock_t printBlock;
}
@property (nonatomic,weak) NSString *name;
- (void)test;
@end
#import "TestCircleRetain.h"
@implementation TestCircleRetain
- (instancetype)init {
self = [super init];
if (self) {
self.name = @"sss";
printBlock = ^{
NSLog(@"print: %@", _name);
};
}
return self;
}
- (void)test {
printBlock();
}
- (void)dealloc {
NSLog(@"TestCircleRetain dealloc");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestCircleRetain *testCircleRetain = [TestCircleRetain alloc] init];
}
return 0;
}
@end
運(yùn)行后可發(fā)現(xiàn)TestCircleRetain實(shí)例類的dealloc方法沒有調(diào)用,因?yàn)樵赥estCircleRetain實(shí)例方法中,Block里使用了 帶有 __strong(強(qiáng)引用) 修飾的testCircleRetain(也就是self)對(duì)象的成員變量 name,所以Block會(huì)捕獲testCircleRetain(而不是只捕獲name,即使你用的是_name,和self.name并無差別),并且當(dāng)Block賦值給成員變量printBlock時(shí),Block由棧上復(fù)制到了堆上 ,因此 testCircleRetain 持有 printBlock,printBlock 持有 testCircleRetain,雙方互相持有(強(qiáng)引用),沒法銷毀,故而沒法執(zhí)行dealloc()方法。不過上面的循環(huán)引用比較明顯,編譯器會(huì)發(fā)現(xiàn)并警告。
如何避免循環(huán)引用
- 使用 __weak(弱引用)修飾的變量
上面的例子中,Block捕獲了帶有 __strong修飾的testCircleRetain對(duì)象,導(dǎo)致testCircleRetain持有的printBlock變量強(qiáng)引用了testCircleRetain自身引起循環(huán)引用,我們可以使用 __weak 修飾的變量并將testCircleRetain(即self)賦值使用來避免循環(huán)引用。
__weak TestCircleRetain *weakSelf = self;
printBlock = ^{
NSLog(@"print: %@", weakSelf.name);
};
- 使用__block 變量來避免循環(huán)引用
__block block_self = self;
printBlock = ^{
NSLog(@"print: %@", block_self.name);
block_self = nil;
};
使用__block修飾的變量并賦值self(testCircleRetain自身),它們之間的引用便變成了:testCircleRetain 引用了 printBlock,block_self 引用了 testCircleRetain和 printBlock。當(dāng)printBlock執(zhí)行后 循環(huán)引用便會(huì)打破,但是如果你不使用 printBlock的話,便會(huì)持續(xù)循環(huán)引用從而導(dǎo)致內(nèi)存泄漏。