上一篇《Block本质的探究》是在比较简单场景之下去探究的,当然不论怎么复杂的场景,本质的一些东西是不会变的。
这一篇主要来探究一下Block的变量捕获
,下面开始进入正题!
一、 Block接收参数
有下面的一段代码, block接收两个参数,这种方式有什么不一样的呢?
//block定义
void (^myBlock)(int, int) = ^(int num1, int num2){
NSLog(@"Hello Block!-- a=%d - b = %d", num1, num2);
};
//block调用
myBlock(10, 20);
编译后
//block定义
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
//block调用
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 20);
---------- 去除强制转换后 ------
//block定义
void (*myBlock)(int, int) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA)
);
//block调用
myBlock->FuncPtr(myBlock, 10, 20);
__main_block_func_0
这个结构体封装函数增加了两个参数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int num1, int num2) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_bee7ad_mi_0, num1, num2);
}
从上面不难发现,定义的时候传进去的参数没有什么变化,只有在调用的时候把参数分别放到了
FuncPtr(__main_block_func_0)
函数的第二和第三个参数。
二、 Block捕获外部变量
在探究Block
捕获外部变量之前,我们先了解一下Block的变量捕获机制
。
1、 Block
变量捕获机制
Block
为了能够正常访问外部变量,有了一个变量捕获机制。
变量类型 | 捕获到block内部 | 访问方式 |
---|---|---|
局部变量 auto |
✅ | 值传递 |
局部变量 static |
✅ | 指针传递 |
全部变量 static |
❎ | 直接访问 |
补充:
- 局部变量默认就是
auto
修饰,写的时候我们可以省略,即 int num1 = 10; 其实就是auto
int num1 = 10;`auto: 自动变量,离开作用域就销毁
2、 Block
捕获auto
局部变量
我们来看一段代码:
int num1 = 10;
//定义一个block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!-- num1 =%d", num1);
};
num1 = 20; //修改一下num1的值
//block调用
myBlock(); //输出Hello Block!-- num1 =10
编译后
int num1 = 10;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
num1
));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
从上面可以看出
num1
的值直接传到__main_block_impl_0(..)
这个构造函数里了。那既然传递到构造函数里,不难猜测,里面的__main_block_impl_0
结构体肯定会有些变化了,下面来探究一下这些变化。
看下__main_block_impl_0
结构体:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num1;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num1, int flags=0) : num1(_num1) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 首先可以看到多了一个
参数同名的成员 int num1
,这个值就是用来存储外面传进来参数值。- 构造函数后面多了个
:num1(_num1)
,这个是c++
语法,类似把参数_num1
赋值给num1,即num1 = _num1
再看下__main_block_func_0
封装函数调用的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num1 = __cself->num1; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_2a5abb_mi_0, num1);
}
- 相比没有捕获外部变量,多了
int num1 = __cself->num1;
也就是通过__cself
拿到__main_block_impl_0
里面的成员num1
的值,而原来__main_block_impl_0
里面的num1
存储的值就10。
而当执行
num1 = 20
这句代码的时候,改变的仅仅是修改了外面定义的num1
变量的值。这也是为什么最后打印出来的值依旧是 10的原因。//输出Hello Block!-- num1 =10
3、 Block
捕获static
局部变量
我们来看下面的代码:
auto int num1 = 10;
static int age = 10;
//定义一个block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1, age);
};
//block调用
num1 = 20;
age = 20;
//block调用
myBlock(); //输出Hello Block!-- num1 =10 -- age = 20
编译一下,看看有什么不一样
//定义一个block
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
num1,
&age));
//block调用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
我们看到定义的时候传给构造函数的参数不一样了:
auto
修饰的变量,传进去就num1的值
static
修饰的变量,传进去的age的地址值
再看结构体__main_block_impl_0
里面的变化
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num1;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num1, int *_age, int flags=0) : num1(_num1), age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 捕获了两个外部变量
- 与参数
num1
同名的成员num1
来接收外面传进来的值
- 与参数
age
同名的*age
来接收外面传进来的地址值
: num1(_num1), age(_age)
分别进行赋值操作。
再看下__main_block_func_0
封装函数执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num1 = __cself->num1; // bound by copy
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_af9218_mi_0,
num1,
(*age)
);
}
block函数调用的时候, 通过__cself
拿到对应的成员的值
int num1 = __cself->num1;
, 取出的是值int *age = __cself->age;
,取出的是地址值- 注意看
NSLog
函数里面如何使用的这两个值:
(*age)
这个操作就是取出*age
所指向内存地址变量的值
所以,到这里应该不难理解为什么程序运行之后输出的结果是Hello Block!-- num1 =10 -- age = 20
三、 Block访问全局变量
下面来看看 Block
内部访问全局变量
的情况,我们来看下面的代码:
int num1 = 10;
static int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//定义一个block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!-- num1 =%d -- age = %d", num1, age);
};
num1 = 20;
age = 20;
//block调用
myBlock();
}
return 0;
}
编译以后:
#pragma clang assume_nonnull end
int num1 = 10;
static int age = 10;
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_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_f263c3_mi_0,
num1,
age);
}
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;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA));
num1 = 20;
age = 20;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
通过查看编译后的代码,我们可以发现:
__main_block_impl_0
结构体里面并没有增加新的成员变量,即没有捕获外面的变量
__main_block_impl_0 (..)
构造函数也没有接收其他额外的参数。__main_block_func_0 (..)
封装了函数执行逻辑的函数也没有接收额外的参数NSLog (..)
函数调用的时候,直接访问了num1 和 age
所以,为什么程序运行输出的结果是:Hello Block!-- num1 =20 -- age = 20
也是不难理解的。
关于Block捕获外部变量的探究就先到这里啦!!!