block捕获自动变量主要分为:默认情况下和添加__block后进行捕获,下面分别进行讨论
1、默认情况下捕获自动变量
#import <stdio.h>
int main() {
// 捕获默认情况下的自动变量
int tes = 34;
int val = 23;
const char *fmt = "val = %d";
void (^blk) (void) = ^{
printf(fmt,val);
};
blk();
return 0;
}
当block捕获自动变量时,clang转换后的主要源代码为:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), 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) {
const char *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt,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 tes = 34;
int val = 23;
const char *fmt = "val = %d";
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可以看出,block中捕获的自动变量被追加到block结构体__main_block_impl_0后面,其声明的成员变量类型与自动变量类型完全相同,block中没有捕获的自动变量不会被追加。
使用执行block语言时的自动变量fmt和val来初始化__main_block_impl_0结构体实例。
总的来说,默认情况下的捕获自动变量意味着在执行block语法时,捕获的自动变量值被保存到block结构体实例(block本身)中,相当于
值传递
2、捕获有 __block 修饰的自动变量
默认情况下,修改block捕获的自动变量值,编译器会报错
那么如何在block中修改外部自动变量的值,主要有以下2种方法:
a、修改C语言中的静态变量
、静态全局变量
和全局变量
b、使用__block
修饰需要修改的自动变量
1、修改C语言中的静态变量
、静态全局变量
和全局变量
block语法的匿名函数部分简单地变换成了C语言函数,但是从这个变换的函数中访问静态全局变量
和全局变量
并没有任何不同,可直接使用
但是对于静态变量
,转换后的函数原本就不在含有block语法的函数里面,超出了静态变量
的作用域,所以不能访问静态变量。
#import <stdio.h>
// 全局变量
int global_val = 1;
// 静态全局变量
static int static_global_val = 2;
int main() {
static int static_val = 3;
void (^blk) (void) = ^{
global_val *=1;
static_global_val *=2;
static_val *= 3;
};
blk();
return 0;
}
clang转换后的源代码为:
int global_val = 1;
static int static_global_val = 2;
// block 的结构体实例
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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 *static_val = __cself->static_val; // bound by copy
global_val *=1;
static_global_val *=2;
(*static_val) *= 3;
}
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() {
static int static_val = 3;
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可以看出,对于静态全局变量
和全局变量
与转换前访问完全相同,但是静态变量
是如何转换的呢?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val;
(*static_val) *= 3;
}
很显然,通过静态变量
static_val的指针对其进行访问,将静态变量
的指针传递给block结构体__main_block_impl_0的构造函数中并保存,这是超出作用域变量的最简单的方法
那么默认情况下的自动变量的访问为什么不采用
静态变量
的访问方式呢?
实际上,block语法生成的值block上,可以存有超过其变量作用域的被捕获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此block中超过变量作用域而存在的变量同静态变量
一样,不能通过指针访问原来的自动变量。
2、使用__block
修饰需要修改的自动变量
更准确的表述方式为“__block存储域类说明符
”,正如C语言中有以下几种存储域类说明符:
- typedef:为某一类型自定义名称
- extern:声明外部全局变量,只能声明,不能实现
- static:作为静态变量存储在数据区中
- auto:作为自动变量存储在栈中
- register:请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。这里是尽可能,不是绝对
__block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中
__block int val = 10;
void (^blk) (void) = ^{
val = 1;
};
clang转换后的源代码为
// __block类型变量转换成结构体的声明
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
// block结构体实例结构,追加了__Block_byref_val_0结构体成员变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_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中如何修改 __block 类型的变量
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->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() {
// __block 类型变量的初始化
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
从上面源代码可以看出,
__block int val = 10;
__block变量同block一样转换成__Block_byref_val_0
类型的结构体实例,存储在栈上,变量初始化值10出现在该结构体实例的初始化中,意味着该结构体持有相当于原自动变量的成员变量
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
__Block_byref_val_0类型的结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
如同初始化的源代码,该结构体中最后的成员变量val相当于原自动变量的成员变量,那么在block中修改自动变量的代码又是被如何转换的,转换后的源代码如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// block中如何修改 __block 类型的变量
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
之前在block中向静态变量
赋值时,使用了指向该静态变量
的指针,但是在__block变量中赋值的过程更为复杂,主要过程如下:
- block的结构体实例__main_block_impl_0持有指向__block变量的__Block_byref_val_0结构体实例的指针
- __Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针
- 通过成员变量__forwarding访问成员变量val,成员变量val是该实例自身持有的变量,相当于原自动变量
多个block使用__block变量,情况如下:
#import <stdio.h>
int main() {
__block int val = 10;
void (^blk0) (void) = ^{
val = 0;
};
void (^blk1) (void) = ^{
val = 1;
};
return 0;
}
clang转换后的源代码为
void (*blk0) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
void (*blk1) (void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
可见多个block同时使用了__Block_byref_val_0结构体实例val的指针,这样就可以实现多个block中使用同一个__block变量,反过来也是可以的,即一个block中使用多个__block变量,只要增加block的结构体__main_block_impl_0成员变量与构造函数的参数
又见block(一):block是什么?
又见block(二):block语法定义
又见block(三):block实质
又见block(五): __block变量和对象
又见block(六):block存储域与__block变量存储域
又见block(七):截获对象