一、概念
Block是带有自动变量值的匿名函数。相当于其他语言里闭包Closure、Lambda的概念。
二、语法
范式
返回值类型 (^Block变量名) 参数列表 = ^返回值类型 参数列表 表达式
其中,返回值类型可省略;参数个数为0时,参数列表可省略。如下示例:
int (^add)(int a, int b) = ^int (int a, int b) {return a + b;};
int (^sum)(int, int) = ^int (int a, int b) {return a + b;};
int (^cc)(void) = ^int (void) { return 0; };
int (^dd)() = ^int () { return 0; };
int (^ee)() = ^{return 0;};
void (^ff)() = ^ { NSLog(@"ff");};
Block类型书写比较复杂,可以使用typedef进行类型定义
typedef int (^TestBlock)(int c);
TestBlock (^hh)(int a, int b) = ^TestBlock (int a, int b) {return ^int (int c) {return 123;};};
Block的执行方式:
int aaa =hh(1,2)(3);
int s = add(1, 2);
^{NSLog(@"abc");}();
三、__block修饰符
__block修饰符,准确地说叫__block存储类说明符(__block storage-class-specifier)。
C语言中存储域说明符包括:typedef、extern、static、auto、register。__block类似于static、auto、register,用于指定将变量值设置到哪个存储域中,比如static变量存储在数据区,auto变量存储在栈中,register变量存储在寄存器中。
__block修饰符只能修饰自动变量,不能修饰其他类型变量。使用__block修饰的自动变量,在Block内部可以修改该自动变量的值。
四、Block的实质
main.m文件源代码:
int main()
{
void (^blk)(void) = ^{};
blk();
}
使用clang(LLVM编译器)将OC代码转换为C++代码,转换命令:clang -rewrite-objc main.m,转换结果main.cpp代码如下:
//Block类,相当于类的结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//Block对象,相当于类的对象的结构体
struct __main_block_impl_0 {
//struct __block_impl impl;展开等价于
//void *isa;
//int Flags;
//int Reserved;
//void *FuncPtr;
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//结构体构造函数
//参数fp为C函数指针
//参数desc为静态全局结构体__main_block_desc_0的实例指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
//&_NSConcreteStackBlock相当于Block类的结构体指针,实现Block对象的isa指向Block类
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//OC语言Block块^{};对应的匿名函数
//通过Block使用的匿名函数被转换为简单的C函数,函数命名规则:__Block所在函数的名称_block_func_Block在函数中序号
//参数__cself为指向Block值的变量,相当于self和this的概念
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
//Block描述信息结构体定义与其静态全局结构体实例__main_block_desc_0_DATA定义
static struct __main_block_desc_0 {
size_t reserved;//保留字段
size_t Block_size;//__main_block_impl_0结构体实例的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
//对应的OC代码:void (^blk)(void) = ^{};
//调用结构体__main_block_impl_0的构造函数创建结构体实例,并赋值给结构体指针blk
//下面为等价代码:
//struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
//struct __main_block_impl_0 *blk = tmp;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//对应的OC代码:blk();
//调用Block,代码实质:(blk->FuncPtr)(blk);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
可见,Block的本质也是对象。
五、捕获变量
Block捕获的变量可以分为两类:
- 没有使用__block修饰的变量,包括全局变量、静态全局变量、静态局部变量、C语言自动变量、强引用的OC对象、弱引用的OC对象。Block中访问不使用__block修饰的变量时,除全局变量和静态全局变量外,Block结构体中会自动追加相应的成员。
- 使用__block修饰的自动变量,包括C语言变量和OC对象。Block中访问__block变量时,变量会自动被转为特定结构体的实例。
C语言自动变量
Block自动将Block内部使用的自动变量作为结构体成员追加到Block结构数据中。由于初始化Block结构体实例时,直接将自动变量的赋值给对应结构成员,属于传值方式,故Block内无法修改外部自动变量。
全局变量、静态全局变量
Block内部可以直接访问全局变量和静态全局变量,也可以进行修改,Block结构体中不会自动追加相应成员变量。
静态局部变量
Block内访问静态局部变量时,Block结构体中会自动追加对应指针类型的成员,属于传地址方式,增加的结构体成员为指针类型,指向静态局部变量,故Block内可以修改静态局部变量的值。
OC对象型自动变量
Block内访问OC对象时,Block结构体会自动追加一个对象所有权类型相同的OC对象成员,该成员会持有外部的OC对象,故在Block内可以调用该OC对象的方法,或向其发送消息。
__block修饰的C语言自动变量
对于使用__block修饰的自动变量,编译器会自动生成一个特定结构体,结构体中包含一个保存实际数据的成员字段,以及一个指向自身类型的指针,Block结构体会追加一个指向该特定结构体的实例的指针成员。
__block修饰的OC对象型变量
对于使用__block修饰的自动变量,编译器会自动生成一个特定结构体,结构体中包含一个保存实际数据的成员字段(对象所有权修饰符也保持一致),以及一个指向自身类型的指针、用于copy对象的函数指针,用于释放对象的函数指针,Block结构体会追加一个指向该特定结构体的实例的指针成员。
示例代码main.m文件源代码如下:
#import <Foundation/Foundation.h>
int global_val = 1;//全局变量
static int static_global_val = 2;//静态全局变量
void (^global_Block)(void) = ^{global_val += 1;};//全局Block
int main()
{
static int static_val = 3;//静态局部变量
int val1 = 10;//自动变量
const char fmt = "val = %d\n";//指针自动变量
__strong id obj1 = [[NSObject alloc] init];//强引用OC对象自动变量
__unsafe_unretained id obj2 = obj1;//__unsafe_unretained类型OC对象自动变量
__weak id obj3 = obj1;//弱引用OC对象自动变量
__block __strong id obj4 = obj1;//__block型强引用OC对象自动变量
__block __weak id obj5 = obj1;//__block型弱引用OC对象自动变量
__block int val2 = 10;//__block型C语言自动变量
//局部Block
void (^blk)(void) = ^{
global_val += 1;
static_global_val += 1;
static_val += 1;
val1;
fmt;
obj1;
obj2;
val2;
obj3;
obj4;
obj5;
};
blk();//执行Block
void (^blk2)(void) = ^{/不截获任何变量的Block*/};
void (^blk_copy)(void) = [blk copy];//复制到堆上的Block
return 0;
}
使用clang(LLVM编译器)将OC代码转换为C++代码,转换命令:clang -fobjc-arc -framework Foundation -rewrite-objc -fobjc-runtime=macosx-10.11 main.m,转换结果main.cpp代码如下:
int global_val = 1;
static int static_global_val = 2;
//全局Block对应的结构体
struct __global_Block_block_impl_0 {
struct __block_impl impl;
struct __global_Block_block_desc_0* Desc;
__global_Block_block_impl_0(void *fp, struct __global_Block_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;//全局Block位于程序静态数据区
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//global_Block全局Block对应的匿名函数
static void __global_Block_block_func_0(struct __global_Block_block_impl_0 *__cself) {
global_val += 1;}
//全局Block对应的描述信息结构体
static struct __global_Block_block_desc_0 {
size_t reserved;
size_t Block_size;
} __global_Block_block_desc_0_DATA = { 0, sizeof(struct __global_Block_block_impl_0)};
//创建全局Block对象,并赋值给该Block类型变量global_Block
static __global_Block_block_impl_0 __global_global_Block_block_impl_0((void *)__global_Block_block_func_0, &__global_Block_block_desc_0_DATA);
void (*global_Block)(void) = ((void (*)())&__global_global_Block_block_impl_0);
//只要声明了__block类型的变量,转换的C++代码中自动为其创建的结构体
//强引用型OC对象__block变量obj4对应的结构体
struct __Block_byref_obj4_0 {
void *__isa;
__Block_byref_obj4_0 *__forwarding;//指向结构体自身,或栈上的__block变量被复制到堆上后,栈上的__block变量的该指针指向堆上的__block变量
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//copy函数的指针
void (*__Block_byref_id_object_dispose)(void*);//释放函数的指针
__strong id obj4;//实际封装的数据,所有权类型保持一致
};
//弱引用型OC对象__block变量obj5对应的结构体
struct __Block_byref_obj5_1 {
void *__isa;
__Block_byref_obj5_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__weak id obj5;
};
//C语言__block自动变量val2对应的结构体
struct __Block_byref_val2_2 {
void *__isa;
__Block_byref_val2_2 *__forwarding;
int __flags;
int __size;
int val2;
};
//blk对应的Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;//对应静态自动变量static_val
int val1;//对应自动变量val1
const char *fmt;//对应自动变量fmt
__strong id obj1;//对应OC自动变量obj1
__unsafe_unretained id obj2;//对应OC自动变量obj2
__weak id obj3;//对应OC自动变量obj3
__Block_byref_val2_2 *val2; //对应__block自动变量val2
__Block_byref_obj4_0 *obj4; //对应__block自动变量obj4
__Block_byref_obj5_1 *obj5; //对应__block自动变量obj5
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int _val1, const char *_fmt, __strong id _obj1, __unsafe_unretained id _obj2, __weak id _obj3, __Block_byref_val2_2 *_val2, __Block_byref_obj4_0 *_obj4, __Block_byref_obj5_1 *_obj5, int flags=0) : static_val(_static_val), val1(_val1), fmt(_fmt), obj1(_obj1), obj2(_obj2), obj3(_obj3), val2(_val2->__forwarding), obj4(_obj4->__forwarding), obj5(_obj5->__forwarding) {
impl.isa = &_NSConcreteStackBlock;//位于栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//栈上Block对应的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val2_2 *val2 = __cself->val2; // bound by ref
__Block_byref_obj4_0 *obj4 = __cself->obj4; // bound by ref
__Block_byref_obj5_1 *obj5 = __cself->obj5; // bound by ref
int *static_val = __cself->static_val; // bound by copy
int val1 = __cself->val1; // bound by copy
const char *fmt = __cself->fmt; // bound by copy
__strong id obj1 = __cself->obj1; // bound by copy
__unsafe_unretained id obj2 = __cself->obj2; // bound by copy
__weak id obj3 = __cself->obj3; // bound by copy
global_val += 1;
static_global_val += 1;
(*static_val) += 1;
val1;
fmt;
obj1;
obj2;
(val2->__forwarding->val2);
obj3;
(obj4->__forwarding->obj4);
(obj5->__forwarding->obj5);
}
//copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj1, (void*)src->obj1, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->obj2, (void*)src->obj2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->val2, (void*)src->val2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj3, (void*)src->obj3, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->obj4, (void*)src->obj4, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj5, (void*)src->obj5, 8/*BLOCK_FIELD_IS_BYREF*/);}
//释放函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj1, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->obj2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->val2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj3, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->obj4, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj5, 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};
//blk2对应的Block结构体
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//位于栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
}
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
int main()
{
static int static_val = 3;
int val1 = 10;
const char *fmt = "val = %d\n";
__attribute__((objc_ownership(strong))) id obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(none))) id obj2 = obj1;
__attribute__((objc_ownership(weak))) id obj3 = obj1;
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(strong))) __Block_byref_obj4_0 obj4 = {(void*)0,(__Block_byref_obj4_0 *)&obj4, 33554432, sizeof(__Block_byref_obj4_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, obj1};
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_obj5_1 obj5 = {(void*)0,(__Block_byref_obj5_1 *)&obj5, 33554432, sizeof(__Block_byref_obj5_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, obj1};
__attribute__((__blocks__(byref))) __Block_byref_val2_2 val2 = {(void*)0,(__Block_byref_val2_2 *)&val2, 0, sizeof(__Block_byref_val2_2), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val, val1, fmt, obj1, obj2, obj3, (__Block_byref_val2_2 *)&val2, (__Block_byref_obj4_0 *)&obj4, (__Block_byref_obj5_1 *)&obj5, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
void (*blk2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
void (*blk_copy)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)blk, sel_registerName("copy"));
return 0;
}
通常C语言结构体中不能包含OC对象型成员,因为编译器不能很好地对C语言里OC对象进行内存管理,但是,Block结构体中可以包含OC对象,因为编译自动生成了copy和dispose函数实现对OC对象成员的内存管。copy函数在Block从栈上复制到堆上时被调用,dispose函数在堆上的Block被释放回收时被调用。
六、存储域
Block的实质:Block的结构体实例自动变量,Block也是OC对象。__block变量的实质:栈上__block变量的结构体实例自动变量。根据Block存储域,Block对应的类有三种:
- _NSConcreteStackBlock:存储域为栈的Block对象可以看作该类的实例,通常在函数内部定义的Block即存储在栈上。
- _NSConcreteGlobalBlock:存储域为程序静态数据区的Block对象可以看作该类的实例,全局形式的Block、函数内不使用应截获的自动变量的Block存储在静态数据区。
- _NSConcreteMallocBlock:存储域为堆的Block对象可以看作该类的实例,作为函数返回值的Block、copy得到的Block存储在堆上。
Block从栈复制到堆上时,栈上的__block变量被复制到堆上并被堆上Block持有,栈上被Block捕获的OC对象自动变量也会被堆上Block持有。所以,copy后,栈上的OC对象自动变量和__block变量可以超过作用域而存在。Block从栈上被复制到堆上本质都是调用copy方法,有四种情况:
- 调用Block的copy方法时
- Block作为函数的返回值时
- 将Block赋值给__strong修饰的id类型的类或Block类型成员变量时
- 在方法名包含usingBlock的Cocoa框架方法或GCD的API中传递Block时
七、Block循环引用
如果在Block中使用了附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆上时,该对象会被堆上Block所持有,如果堆上的Block又由该对象所直接或间接持有,这样就导致循环引用。避免循环引用的三种方式:
使用__weak修饰符
使用__unsafe_unretained修饰符
-
使用__block修饰符并手动置nil,优点是可以控制对象的持有期,缺点是必须执行Block,并执行到置nil的语句。
@interface MyObject : NSObject @end @implementation MyObject { void (^_blk1)(void); void (^_blk2)(void); void (^_blk3)(void); void (^_blk4)(void); void (^_blk5)(void); void (^_blk6)(void); id _obj; id _obj2; } - (id)init { self = [super init]; _obj = [NSObject new]; _obj2 = [NSObject new]; _blk1 = ^{ NSLog(@"%@", self); };//Block内捕获并强引用对象self,而self又强引用Block,产生循环引用,导致内存泄漏 _blk2 = ^{ NSLog(@"%@", _obj); };//等效于self->_obj,Block实际截获了self,对于编译器,_obj不过是结构体成员,同样产生循环引用,导致内存泄漏 __weak typeof(self) weakSelf = self; _blk2 = ^{ NSLog(@"%@", weakSelf); };//使用__weak修饰符避免循环引用 __weak typeof(_obj) weakObj = _obj; _blk4 = ^{ NSLog(@"%@", weakObj); };//使用__weak修饰符避免循环引用 __unsafe_unretained typeof(_obj) weakObj = _obj; _blk4 = ^{ NSLog(@"%@", weakObj); };//在不支持__weak的时候,可使用__unsafe_unretained修饰符避免循环引用 __block id tmp = self; _blk6 = ^{ NSLog(@"%@", tmp); tmp = nil;};//使用__block变量避免循环引用,要求程序必须能执行到该block中,才能通过将tmp置为nil,断开循环引用 return self; } @end