一、block 引用外部变量时,对外部变量的捕获(capture)情况
1. 引用全局变量的block,能简单说下ta的底层结构吗?
#import
static int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^lspBlock)(void) = ^{
NSLog(@"我是简单的block:%d",age);
};
lspBlock();
}
return 0;
}
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
将代码转换成 C++代码,有助于我们观察代码的本质
- 转换获得如下代码
- 从上图,我们可以获得一些信息:
- ① block内部有
isa
指针,说明 Block 是一个 OC类
- ② block 类中存储着
需要运行的函数指针FuncPtr
,以及对 block的描述信息 __main_block_desc_0
- ③
__mian_block_impl_0
结构体中没有 age
的信息,说明block
没有对全局变量 age
进行捕获
2. 引用局部静态变量的block,能简单说下ta的底层结构吗?
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
void (^lspBlock)(void) = ^{
NSLog(@"我是简单的block:%d",age);
};
lspBlock();
}
return 0;
}
- 从转换后的代码,我们可以清晰的看到,对于静态局部变量,1block
会采取捕获指针的方式,拿到
&age的地址值,赋值给
__main_block_impl_0` 结构体对象
3. 引用局部变量的block,能简单说下ta的底层结构吗?
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^lspBlock)(void) = ^{
NSLog(@"我是简单的block:%d",age);
};
lspBlock();
}
return 0;
}
- 我们可以发现,
__main_block_impl_0
中传递的是 age
这个值,说明block捕获局部变量时,是值捕获。
4. 接下来我们总结一下 block引用外部变量,在各种情况下的捕获情况
5. 明白了 block 捕获原理后,我们来强化一下,请问下面代码的输出是多少?
#import
int globalAge = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
static int staticAge = 30;
void (^lspBlock1)(void) = ^{
NSLog(@"我是简单的block:%d",globalAge);
};
void (^lspBlock2)(void) = ^{
NSLog(@"我是简单的block:%d",age);
};
void (^lspBlock3)(void) = ^{
NSLog(@"我是简单的block:%d",staticAge);
};
globalAge++;
age++;
staticAge++;
lspBlock1();
lspBlock2();
lspBlock3();
}
return 0;
}
Demo[6041:138667] 我是简单的block:11
Demo[6041:138667] 我是简单的block:20
Demo[6041:138667] 我是简单的block:31
6.请问下面的 block 有捕获变量吗?捕获的是谁?
#import "Person.h"
@interface Person ()
@property(nonatomic, assign) int age;
@end
@implementation Person
- (void)lspTest {
void (^block)(void) = ^{
NSLog(@"%i", _age);
};
block();
}
@end
-
_age
实际上是 self->age
,而 self
是一个局部实例变量,对于局部变量,block 会采取值捕获 self
- 还有一个疑惑,思考为什么我们在类中调用方法,可以直接使用
self
和 _cmd
这两个变量
- 这是因为OC底层替我们在方法中传递了这两个参数,比如上述代码转换后
static void _I_Person_lspTest(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__lspTest_block_impl_0((void *)__Person__lspTest_block_func_0, &__Person__lspTest_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
二、block 的类型
1. 除了 block 有 isa
指针这个方面来说,我们还可以从哪方面证明 block 是一个 OC 对象?
- 我们还可以在
MRC
下通过打印 block 的 class 和 superclass来说明;
#import
int globalAge = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
static int staticAge = 30;
void (^lspBlock1)(void) = ^{
NSLog(@"我是简单的block:%d",globalAge);
};
void (^lspBlock2)(void) = ^{
NSLog(@"我是简单的block:%d",age);
};
void (^lspBlock3)(void) = ^{
NSLog(@"我是简单的block:%d",staticAge);
};
void (^lspBlock4)(void) = [lspBlock2 copy];
NSLog(@"lspBlock1: %@, %@, %@, %@",[lspBlock1 class],
[lspBlock1 superclass],
[[lspBlock1 superclass] superclass],
[[[lspBlock1 superclass] superclass] superclass]);
NSLog(@"lspBlock2: %@, %@, %@, %@",[lspBlock2 class],
[lspBlock2 superclass],
[[lspBlock2 superclass] superclass],
[[[lspBlock2 superclass] superclass] superclass]);
NSLog(@"lspBlock3: %@, %@, %@, %@",[lspBlock3 class],
[lspBlock3 superclass],
[[lspBlock3 superclass] superclass],
[[[lspBlock3 superclass] superclass] superclass]);
NSLog(@"lspBlock4: %@, %@, %@, %@",[lspBlock4 class],
[lspBlock4 superclass],
[[lspBlock4 superclass] superclass],
[[[lspBlock4 superclass] superclass] superclass]);
}
return 0;
}
Demo[6201:154701] lspBlock1: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock2: __NSStackBlock__, __NSStackBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock3: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock4: __NSMallocBlock__, __NSMallocBlock, NSBlock, NSObject
- 我们发现block最终继承于
NSObject
,再次证明 block 底层就是OC对象
- 我们还发现,我们打印的block中主要有三种类型
__NSGlobalBlock
、__NSStackBlock
、__NSMallocBlock
- 还发现,无论哪种类型的block都继承于
NSBlock
2. 既然 block 有三种类型,你能从它们的名字,区分出运行时,它们分别放在内存的哪一段吗?
-
__NSGlobalBlock
放在数据区,特点是:由系统管理,运行过程不会释放
-
__NSStackBlock
放在栈区,特点是:系统自动分配内存,也会自动释放内存
-
__NSMallocBlock
放在堆区,特点是:需要程序员自己管理内存
3. 既然 block 有三种类型,那对它们进行 copy 操作会有什么效果呢?
4. 你可能会遇到的疑惑,如果将 1
中的代码,转换成 c++ 代码
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int age;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我们重点注意这句话
impl.isa = &_NSConcreteStackBlock;
,我们发现无论哪种 block,C++代码的都会是这句代码
- 这句话的含义就是将
_NSConcreteStackBlock
这个类对象赋值给 isa
指针
- 也就说明,从C++代码层面,这些block都是
_NSConcreteStackBlock
,怎么回事呢?
- 当我们遇到这种情况,
一切以运行时的情况为准
,因为运行时的结果,才是我们真正的最终结果