什么是block
block是带有局部变量的匿名函数
- 局部变量:作用域仅在block内的变量, 由block持有
block语法
^(返回值)(参数列表){表达式}
ex:
^(int)(string s1, string s2) {
log("%s, %s", s1, s2)
}
其中 ,返回值类型可以省略,参数若为空,则参数列表也可以省略
//这是最简单的block结构
^{表达式}
block 的三种类型
- 全局block NSGlobalBlock
- 栈上block NSStackBlock
- 堆上block NSMallocBlock
三种block的主要区别在于其生命周期和作用域
全局block
全局block作用于全局 ,随应用进程的结束而结束,并且无法对外部变量进行捕获
栈block
栈上block作用于当前声明block变量的函数中,函数返回,block声明周期结束,可以捕获局部变量,静态局部变量会以指针的形式捕获。在ARC中,一旦栈block捕获变量,则会变成堆block
堆block
堆上block则是作用在堆上,由ARC对其进行生命周期的管理,MRC下只有进行copy后才会将block迁移至堆区
block的变量捕获
- 基本数据类型的局部变量 Block 可以截获其值
- 对于对象类型的局部变量连同所有权修饰符一起截获
- 局部静态变量以指针的形式进行截获
- 全局变量和静态全局变量,block 是不截获的
下面来看一下代码
int global_var = 10;
void(^global_blk)(void) = ^{
printf("sss: %d\n", global_var);
};
void(^stack_blk)(void);
int main() {
//c语言中,block无法捕获数组字面量,此处用指针替代
char *local_var = "local_var";
static char *local_static_var = "local_static_var";
stack_blk = ^{
printf("global_var: %d\nlocal_var: %s\nlocal_static_var: %s\n", global_var, local_var, local_static_var);
};
global_blk();
stack_blk();
global_var = 20;
local_var = "local_var_change";
local_static_var = "local_static_var_change";
global_blk();
stack_blk();
return 0;
}
上面代码中,定义了一个全局变量global_var,一个全局的block,并声明了一个block变量来承载一个栈block (OC在ARC环境下栈block会被自动copy进堆区,所以这里使用了c语言),main函数里定义两个变量,分别是局部变量和静态局部变量,并在两次block调用期间,修改了一次变量信息
上述代码运行结果:
sss: 10
global_var: 10
local_var: local_var
local_static_var: local_static_var
sss: 20
global_var: 20
local_var: local_var
local_static_var: local_static_var_change
从结果可以看出,全局变量在被修改了数据后,block内的值也受到了影响而改变。而局部变量和局部静态变量被修改了数据后,则表现得有些不一样。
可以看到,名字为local_var的局部变量,在外部被修改了值以后,在block内部依旧打印出了原来的值,而名字为local_static_var的静态全局变量的值则是内外一同发生了改变,这是为什么?
借由clang可以把文件编译成oc的cpp实现,我们来试一下
控制台键入命令
//我的文件名是main.c
clang -rewrite-objc main.c
去掉一些自动生成的冗余代码,剩余代码如下,有些长,可以先跳过
int global_var = 10;
struct __global_blk_block_impl_0 {
struct __block_impl impl;
struct __global_blk_block_desc_0* Desc;
__global_blk_block_impl_0(void *fp, struct __global_blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __global_blk_block_func_0(struct __global_blk_block_impl_0 *__cself) {
printf("sss: %d", global_var);
}
static struct __global_blk_block_desc_0 {
size_t reserved;
size_t Block_size;
} __global_blk_block_desc_0_DATA = { 0, sizeof(struct __global_blk_block_impl_0)};
static __global_blk_block_impl_0 __global_global_blk_block_impl_0((void *)__global_blk_block_func_0, &__global_blk_block_desc_0_DATA);
void(*global_blk)(void) = ((void (*)())&__global_global_blk_block_impl_0);
void(*stack_blk)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
char *local_var;
char **local_static_var;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_local_var, char **_local_static_var, int flags=0) : local_var(_local_var), local_static_var(_local_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
char *local_var = __cself->local_var; // bound by copy
char **local_static_var = __cself->local_static_var; // bound by copy
printf("global_var: %d\n local_var: %s\n local_static_var: %s\n", global_var, local_var, (*local_static_var));
}
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() {
char *local_var = "local_var";
static char *local_static_var = "local_static_var";
((void (*)(__block_impl *))((__block_impl *)global_blk)->FuncPtr)((__block_impl *)global_blk);
stack_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_var, &local_static_var));
((void (*)(__block_impl *))((__block_impl *)stack_blk)->FuncPtr)((__block_impl *)stack_blk);
return 0;
}
先来看一下为什么某些变量在block外部被修改,而变量在block内部的值不变
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
char *local_var;
char **local_static_var;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_local_var, char **_local_static_var, int flags=0) : local_var(_local_var), local_static_var(_local_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这是代表着我们栈block的结构体,可以看到,结构体内部有两个变量,名字分别是我们在使用中定义的名字,这两个变量就是在block执行过程中,被copy进来的,但是有一些区别,local_var是以变量类型进行捕获,而静态局部变量local_static_var则是以指针的形式进行捕获。
这也就意味着,在我修改静态局部变量时,修改的是其引用,所以不受影响
关于__block
有没有发现,当我在block内部修改一个局部变量的时候,会报出一个编译错误,意思是要修改的变量需要一个__block修饰符,我们先加一个修饰符给报错的变量
但是,为什么上那个名字是local_static_var的静态局部变量没事儿呢,我们先来看一下clang后的结果
struct __Block_byref_local_var_0 {
void *__isa;
__Block_byref_local_var_0 *__forwarding;
int __flags;
int __size;
char *local_var;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
char **local_static_var;
__Block_byref_local_var_0 *local_var; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char **_local_static_var, __Block_byref_local_var_0 *_local_var, int flags=0) : local_static_var(_local_static_var), local_var(_local_var->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们可以发现,多了一个叫做__Block_byref_local_var_0的结构体,这个结构体就是被我们用__block修饰后的那个变量。
而在我们原来的block结构体中,多了一个__Block_byref_local_var_0 *local_var; 变量,这个变量就是对__block修饰后变量的引用,通过修改引用就可以来修改被block捕获的变量。
通过之前的代码分析得到静态局部变量在block结构体中也是以引用的形式存在的,所以,block内部修改静态局部变量不会引发编译错误