探索场景需求
- 局部变量
- 全局静态变量
- 局部对象(举例NSMutableArray)
- 栈对象(NSString *str = @"AAA"; 这种情况,情况个例)
我们会根据这几种情况,根据打印指针,打印结果,还有探索源码进行分析。
先说结论
- 局部变量首先在heap区开辟空间, 被捕获后copy变量值,另外在stack区开辟一个新的空间存储,block执行后显示的是最开始的数据。
- 全局静态变量在static区开辟空间,被捕获不影响其地址,捕获后内部使用仍然是其static区的地址,所以显示变化后的数据。
- 局部对象NSMutabblArray,是在heap堆区开辟内存空间,被捕获后还是捕获其原地址指针,所以block执行会根据可变数组变化显示。
- 栈对象。同局部变量一样不展开。
设计相关代码
//此处是设计的变量、局部对象
int a = 10;
static int b = 100;
NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
NSLog(@"a: %p",&a);
NSLog(@"b: %p",&b);
NSLog(@"array1: %p",array1);
self.myblock = ^(SecViewController * aa) {
NSLog(@"a:%d",a);
NSLog(@"a: %p",&a);
NSLog(@"b: %d",b);
NSLog(@"b: %p",&b);
NSLog(@"array1:%p",array1);
};
a = 20;
b = 200;
[array1 addObject:@"3"];
self.myblock(self);
查看一下打印结果:
2019-12-25 19:52:14.569872+0800 BlockTest[9499:776114] a: 0x7ffee83f7fac
2019-12-25 19:52:14.570121+0800 BlockTest[9499:776114] b: 0x107808828
2019-12-25 19:52:14.570277+0800 BlockTest[9499:776114] array1: 0x600003598720
2019-12-25 19:52:32.933938+0800 BlockTest[9499:776114] a:10
2019-12-25 19:52:33.675075+0800 BlockTest[9499:776114] a: 0x600003598b38
2019-12-25 19:52:34.803982+0800 BlockTest[9499:776114] b: 200
2019-12-25 19:52:35.967736+0800 BlockTest[9499:776114] b: 0x107808828
2019-12-25 19:52:47.685754+0800 BlockTest[9499:776114] array1:0x600003598720
从打印的数据可以验证结论:
我们局部变量,在block内被捕获,但是通过观察地址,我们发现这个被捕获的局部变量,这是block之前的那个变量进行局部copy而已,地址的指针已经改变。而局部静态变量b一直存在静态区,所以打印的是它变化后的值。对于局部对象数组array1而言,生成时候的地址就是在stack区,block捕获后的地址还是原地址,所以对数组进行操作后,打印出来的是操作后的数据。
探索不同场景下 block 内部实现
首先设置研究环境,我们都知道Object-C中,是通过Clang/LLVM对OC代码进行编译,那我们编译一下block所在的.m文件,查看一下其编译成了什么。
- 首先我们cd 进入其所在的文件夹。
- 通过Clang语音进行编译,然后在对应文件查看相关的C++文件。
xcrun -sdk iphonesimulator clang -rewrite-objc SecViewController.m
1. 源码解析
static void _I_SecViewController_viewDidLoad(SecViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SecViewController"))}, sel_registerName("viewDidLoad"));
int a = 10;
static int b = 100;
NSMutableArray *array1 = ((NSMutableArray * _Nonnull (*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("arrayWithObjects:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_1, __null);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_2,&a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_3,&b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_4,array1);
((void (*)(id, SEL, myBlock))(void *)objc_msgSend)((id)self, sel_registerName("setMyblock:"), ((void (*)(SecViewController *))&__SecViewController__viewDidLoad_block_impl_0((void *)__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
a = 20;
b = 200;
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array1, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_10);
((myBlock (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("myblock"))(self);
}
看起来乱糟糕,让我们只提取一下block的信息出来也就是
self.myblock = ^(SecViewController * aa) {
NSLog(@"a:%d",a);
NSLog(@"a: %p",&a);
NSLog(@"b: %d",b);
NSLog(@"b: %p",&b);
NSLog(@"array1:%p",array1);
};
对应的源码如下:
((void (*)(id, SEL, myBlock))(void *)objc_msgSend)((id)self, sel_registerName("setMyblock:"), ((void (*)(SecViewController *))&__SecViewController__viewDidLoad_block_impl_0((void *)__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
再简化一下,去掉相关的类型编译
( (id, SEL, myBlock) objc_msgSend)(self, sel_registerName("setMyblock:"), (&__SecViewController__viewDidLoad_block_impl_0(__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
我们看到上面代码。形参id SEL myBlock,我们再抽离一下,只看看myBlock 对应的代码
&__SecViewController__viewDidLoad_block_impl_0(__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344))
我们看到这里的时候需要进去再看看__SecViewController__viewDidLoad_block_impl_0
这个到底是什么东东? 根据了解信息猜测,这应该就是myblock在heap区下对应的block信息,看看源码验证一下
struct __SecViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __SecViewController__viewDidLoad_block_desc_0* Desc;
int a;
int *b;
NSMutableArray *array1;
__SecViewController__viewDidLoad_block_impl_0(void *fp, struct __SecViewController__viewDidLoad_block_desc_0 *desc, int _a, int *_b, NSMutableArray *_array1, int flags=0) : a(_a), b(_b), array1(_array1) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
果然,如咱们所料,这个就是代码中myBlock下对应的信息。在这里我们可以初步看到,结构体内部拿到的参数中,a是变量,b、 array1是指针。
此处 fp的实参是什么呢?根据上面myBlock内部代码我们很容易发现是__SecViewController__viewDidLoad_block_func_0
,源码如下:
static void __SecViewController__viewDidLoad_block_func_0(struct __SecViewController__viewDidLoad_block_impl_0 *__cself, SecViewController *aa) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSMutableArray *array1 = __cself->array1; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_5,a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_6,&a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_7,(*b));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_8,&(*b));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_9,array1);
}
在这里我们就发现了,此处拿到的a只是一个值传递,b、array1是指针也就是地址传递,所以我们在内部打印时候,只能打印捕获到的a数据,但是通过指针可以获取到改变后的数据b、array1.
综上:
我们block内只能操纵局部变量原始数据,可以捕获静态变量、堆对象的最新改变后的数据。
Git项目代码:下载Block
网友评论