前言
说下block的变量捕获
修改变量
首先我们看个问题,如果我想修改a的值 都有哪些方法呢
代码
int a = 3;
void (^yang)(void) = ^{
NSLog(@"a=%d", a);
};
a = 9;
yang();
方式一: 使用static
// 局部静态变量存储全局区 可以在block内部直接访问修改
static int a = 3;
NSLog(@"%p",&a);
void (^yang)(void) = ^{
NSLog(@"a=%d", a);
NSLog(@"%p",&a);
a = 12;
};
a = 9;
yang();
NSLog(@"a=%d", a);
2021-11-26 19:04:47.060336+0800 04-捕获变量[29418:37048995] a=9
2021-11-26 19:04:47.060985+0800 04-捕获变量[29418:37048995] a=12
分析
局部静态变量存储全局区 可以在block内部直接访问修改
方式二:使用指针
代码
int a = 3;
int *p = &a;
void (^yang)(void) = ^{
NSLog(@"a=%d", a);
*p = 12;
};
*p = 5;
NSLog(@"a=%d", a);
yang();
NSLog(@"a=%d", a);
输出
2021-11-26 19:09:40.274657+0800 04-捕获变量[30725:37058135] a=5
2021-11-26 19:09:40.275240+0800 04-捕获变量[30725:37058135] a=3
2021-11-26 19:09:40.275341+0800 04-捕获变量[30725:37058135] a=12
方式三:加 __block
代码
__block int a = 3;
NSLog(@"%p", &a);
void (^yang)(void) = ^{
NSLog(@"%p", &a);
NSLog(@"a=%d", a);
};
NSLog(@"%p", &a);
a = 9;
yang();
NSLog(@"%p", &a);
这里有个误区: 很多人认为 加上__block是把a的地址传递给了block。
输出
2021-11-26 19:36:01.983998+0800 04-捕获变量[37788:37107042] 0x7ffee2bf5bc0
2021-11-26 19:36:01.984703+0800 04-捕获变量[37788:37107042] 0x600001b04b78
2021-11-26 19:36:01.984809+0800 04-捕获变量[37788:37107042] 0x600001b04b78
2021-11-26 19:36:01.984910+0800 04-捕获变量[37788:37107042] a=9
2021-11-26 19:36:01.985020+0800 04-捕获变量[37788:37107042] 0x600001b04b78
总结:
1、 如果__block是传递地址 a的地址应该一样
2、 使用static 的方法才是真正的传递地址(看上面的static例子)
3、 block内部能访问a的值是变量捕获机制
4、 能修改a的值是 block变量结构体__block_ref的功劳
变量捕获
变量捕获我们按照基本类型和对象类型来说
基本类型变量
int a = 1;
static int b = 2;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int c = 3;
auto int d = 3;
static int e = 4;
void (^block)(void) = ^{
NSLog(@"a=%d, b=%d, c=%d, d=%d, e=%d", a, b, c, d, e);
};
a = 9;
b = 9;
c = 9;
d = 9;
e = 9;
block();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Clang
block结构体结构
int a = 1;
static int b = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int d;
int *e;
//__main_block_impl_0 中后面的 : c(_c), d(_d), e(_e) 是干什么用的
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int _d, int *_e, int flags=0) : c(_c), d(_d), e(_e) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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)};
block匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int c = __cself->c; // bound by copy
int d = __cself->d; // bound by copy
int *e = __cself->e; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_main_416883_mi_0, a, b, c, d, (*e))
}
block实现和调用
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
int c = 3;
auto int d = 3;
static int e = 4;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e));
a = 9;
b = 9;
c = 9;
d = 9;
e = 9;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
问题
__main_block_impl_0 中后面的 : c(_c), d(_d), e(_e) 是干什么用的
C++允许结构体构造函数后面跟初始化参数列表
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e));
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int d;
int *e;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int _d, int *_e, int flags=0) : c(_c), d(_d), e(_e) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
等价于
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int d;
int *e
};
// 创建结构体变量 imp0
struct __main_block_impl_0 imp0;
// 进行成员赋值
_c = c;
imp0.c = _c
_d = d;
imp0.d = _c
_e = e;
imp0.e = _e
输出
// c d 的值没有改变 其他的变量都改变了
2021-11-25 17:16:16.048375+0800 04-捕获变量[83640:34293820] a=9, b=9, c=3, d=3, e=9
语法
函数中可能使用的变量种类:
- 自动变量(局部变量)
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中
- 静态变量
- 静态全局变量
- 全局变量
由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用.
- static修饰的变量为静态变量, static类型的变量(不论是全局还是局部),其值一直保留在内存中,不受大括号的限制,程序结束时才被销毁
- 所有没有修饰符的局部变量默认的修饰符为auto,auto类型的局部变量的生命周期为离其最近的大括号内,超出大括号,该变量被自动销毁
如图
变量传递
__main_block_impl_0 (__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e)
该构造方法 值传递 c d 和 地址传递e
分析
- a、b为全局变量 c d e 为局部变量
- c 和 d 保持不变依然为int型变量,e被转换成int型指针变量
- block结构里中存在 c d e 而不存在 a b 变量 把存在于block结构体中的外部变量称为变量捕获,不存在的则没有被捕获
- auto变量随着大括号就消失了 捕获地址没用呀 函数栈结束 就没有你的地址了,保存有啥意义。
- 全局变量有自己独立的存储空间, 一旦生成地址不会修改, main函数可以用 block 也可以拿地址用, 没必要再保存到block内。
- static修饰的局部变量会被转化成指针变量 讲地址传递给block 所以block 可以修改
- auto局部变量被捕获,block 拿进来的值 是值传递 。 内部自己也有一个成员变量去接受这个值, 这个变量和外部的变量 是2个地址
对象类型
对象类型的auto变量 在使用 __block 和 __strong 修饰时候
代码等价
__block NSObject *obj3 = [[NSObject alloc] init];
__block __strong NSObject *obj3 = [[NSObject alloc] init];
__block NSObject *__strong obj3 = [[NSObject alloc] init];
__strong __block NSObject *obj3 = [[NSObject alloc] init];
这个也等价
NSObject *obj1 = [[NSObject alloc] init];
NSObject *__strong obj1 = [[NSObject alloc] init];
__strong NSObject *obj1 = [[NSObject alloc] init];
代码
__block int age = 0;
NSObject *obj1 = [[NSObject alloc] init];
__weak NSObject *obj2 = [[NSObject alloc] init];
__block NSObject *obj3 = [[NSObject alloc] init];
// 不能反过来(如:__weak __block),因为__weak只能修饰OC对象
__block __weak NSObject *obj4 = [[NSObject alloc] init];
void (^yang)() = ^{
NSLog(@"age==%@",age);
NSLog(@"obj==%@",obj1);
NSLog(@"obj==%@",obj2);
NSLog(@"obj==%@",obj3);
NSLog(@"obj==%@",obj4);
};
clang 对比代码
分析
- ARC下对象变量默认被 __strong 修饰
- 加 __block修饰就生成 block变量 (__Block_byref结构体)
- 捕捉之后 结构体内部对对象的引用是强引用还是弱引用和外面保持一致
比如:
obj1 obj3是强引用内部就是__strong
objc2 objc4 就是弱引用 __weak
__block修改变量
分析下代码报错的原因
代码
Clang 对比代码
)
分析
2 3地址不一样 所以修改的2地址上的a block打印的就不是同一个对象
a 在block内部也不能修改, 提示我们加 __block 关键字
加上 __block
__block int a = 0;
NSLog(@"1==%p",&a);
void (^yang)(void) = ^{
NSLog(@"a=%d", a);
NSLog(@"3==%p",&a);
a = 13;
};
NSLog(@"2==%p",&a);
a = 9;
yang();
NSLog(@"a=%d", a);
NSLog(@"4==%p",&a);
输出
2021-11-26 18:08:13.839286+0800 04-捕获变量[14289:36949370] 1==0x7ffee78d8bc0
2021-11-26 18:08:13.840071+0800 04-捕获变量[14289:36949370] 2==0x600002b208b8
2021-11-26 18:08:13.840287+0800 04-捕获变量[14289:36949370] a=9
2021-11-26 18:08:13.840414+0800 04-捕获变量[14289:36949370] 3==0x600002b208b8
2021-11-26 18:08:13.840524+0800 04-捕获变量[14289:36949370] a=13
2021-11-26 18:08:13.840647+0800 04-捕获变量[14289:36949370] 4==0x600002b208b8
问题
那么为什么加 __block 就可以修改局部变量的值呢 ?
__block 都干了什么呢?
简单说就是
自动变量加上__block修饰符之后,会改变这个自动变量的存储区域
分析
- 基本类型变量和对象类型变量都可以被__block 修饰
- 被__block修改的变量叫block变量
- __block变量如果是auto类型 会改变对象的存储区域我们看到上面地址输出 从栈到了堆
后面的赋值操作都是在新的地址上进行的,从地址2 开始 变量a地址就被修改了 后续 block内部和外部修改的地址都是都一个地址
这个堆地址是如何生成的? 我们继续看
底层分析
Step1
__block修饰符 首先告诉编译器 这是一个block变量
Step2
block变量就会生成 __Block_byref结构体
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
__isa指针 :__Block_byref_a_0中也有isa指针也就是说__Block_byref_a_0本质也一个对象。
__forwarding :__forwarding是__Block_byref_a_0结构体类型的
__flags :0
__size :sizeof(__Block_byref_a_0)即__Block_byref_a_0所占用的内存空间。
a:真正存储变量的地方, 在栈a临死前把它的值给了这里的a ,我们打印的地址 就是这个a的地址 我们修改的a 也是这个a
)
Step3
block变量a(__Block_byref_a_0) 把外界变量a的值捕获到自己内部
__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体自己的内存地址。
Step4
把自己交给block对象 (__main_block_impl_0) 此时block对象(__main_block_impl_0) 已经持有block变量,开启了一块新的内存 放到了自己的内存布局中。
内存布局
Step5
我们在block内修改一下a
__block int a = 3;
NSLog(@"%p", &a);
void (^yang)(void) = ^{
NSLog(@"%p", &a);
NSLog(@"a=%d", a);
a = 13;
NSLog(@"a=%d", a);
};
NSLog(@"%p", &a);
a = 9;
yang();
NSLog(@"%p", &a);
分析
之后我们调用block 触发__main_block_func_0 函数
__main_block_func_0 函数内部 先拿到a结构体对象
通过a结构体拿到__forwarding指针
上面提到过__forwarding中保存的就是__Block_byref_a_0结构体本身,这里也就是a
再通过__forwarding拿到结构体中的a变量并修改其值
经过以上几步block变量的值得到了修改
以上就是捕获变量和修改变量的完整流程
问题
为什么搞这么复杂 中间用一个__forwarding指针? 看我这篇文章: block __forwarding 指针