面试中经常会被问到什么是 Block, 谈谈你对 Block 的理解, 今天就做一个小的总结。
Block 定义
block 是将函数及其上下文封装起来的对象
首先, 定义一个简单的 AddBlock
- (void)test1 {
void(^AddBlock)(NSInteger a , NSInteger b) = ^(NSInteger a, NSInteger b) {
NSLog(@"%ld",a+b);
};
AddBlock(3,4);
}
接着运行相关命令转换成 C++ 文件
clang -rewrite-objc TestBlock.m
__block_impl
struct __block_impl {
void *isa; // isa指针, 验证了 block 也是一个对象
int Flags;
int Reserved;
void *FuncPtr; // 执行调用的函数指针
};
具体调用流程
struct __TestBlock__test1_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__test1_block_desc_0* Desc;
__TestBlock__test1_block_impl_0(void *fp, struct __TestBlock__test1_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__test1_block_func_0(struct __TestBlock__test1_block_impl_0 *__cself, NSInteger a, NSInteger b) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_1v6qx94n2gl27sx0j1dqlrs40000gn_T_TestBlock_54a84e_mi_0,a+b);
}
static struct __TestBlock__test1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __TestBlock__test1_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__test1_block_impl_0)};
static void _I_TestBlock_test1(TestBlock * self, SEL _cmd) {
void(*AddBlock)(NSInteger a , NSInteger b) = ((void (*)(NSInteger, NSInteger))&__TestBlock__test1_block_impl_0((void *)__TestBlock__test1_block_func_0, &__TestBlock__test1_block_desc_0_DATA));
((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)AddBlock)->FuncPtr)((__block_impl *)AddBlock, 3, 4);
}
最终会执行 __TestBlock__test1_block_impl_0
方法, 并把 isa 指针指向了 _NSConcreteStackBlock
, desc
包含了 block 结构体大小等信息
截获变量
定义基本类型和对象相关的全局变量、静态全局变量、局部变量,局部静态变量
#import "TestBlock.h"
static int static_global_a; // 基本类型静态全局变量
int global_a; // 基本类型全局变量
static NSObject *static_global_object; // 静态全局对象
NSObject *global_object; // 全局对象
@implementation TestBlock
- (void)test2 {
static int static_inside_a; // 基本类型局部静态变量
int inside_a; // 基本类型局部变量
static NSObject *static_inside_object;// 静态局部对象
NSObject *inside_object; // 局部对象
static_global_a = 1;
global_a = 2;
static_inside_a = 3;
inside_a = 4;
static_global_object = [[NSObject alloc] init];
global_object = [[NSObject alloc] init];
static_inside_object = [[NSObject alloc] init];
inside_object = [[NSObject alloc] init];
void(^Block)(void) = ^(){
NSLog(@"截获基本数据类型 %d,%d,%d,%d",static_global_a,global_a ,static_inside_a,inside_a);
NSLog(@"截获对象 %@,%@,%@,%@",static_global_object,global_object ,static_inside_object,inside_object);
};
Block();
}
@end
转换后的实现如下
struct __TestBlock__test2_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__test2_block_desc_0* Desc;
int *static_inside_a;
int inside_a;
NSObject **static_inside_object;
NSObject *inside_object;
__TestBlock__test2_block_impl_0(void *fp, struct __TestBlock__test2_block_desc_0 *desc,
int *_static_inside_a, int _inside_a,
NSObject **_static_inside_object, NSObject *_inside_object, int flags=0) : static_inside_a(_static_inside_a), inside_a(_inside_a), static_inside_object(_static_inside_object), inside_object(_inside_object) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以发现 Block 只截获了 基本类型和对象的局部变量, 不同点是静态变量是以指针形式存在的, 并没有截获全局变量、静态全局变量, 对象类型的局部变量连同修饰权一起截获
类型 | 局部 | 静态局部 | 全局 | 静态全局 |
---|---|---|---|---|
基本类型 | ✔️ | ✔️ | ✖️ | ✖️ |
对象 | ✔️ | ✔️ | ✖️ | ✖️ |
__block 修饰符
用简单的话来说, 如果要对截获的变量进行赋值操作, 需要添加 __block
修饰符
用下面的例子看一下 __block 的具体实现
- (void)test3 {
__block int b = 2;
void(^Block)(void) = ^(){
b = 4; // __block 修饰后可以直接赋值
NSLog(@"%d",b);
};
Block();
}
__block 修饰的对象转变成结构体 __Block_byref_b_0
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
struct __TestBlock__test3_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__test3_block_desc_0* Desc;
__Block_byref_b_0 *b; // by ref
__TestBlock__test3_block_impl_0(void *fp, struct __TestBlock__test3_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__test3_block_func_0(struct __TestBlock__test3_block_impl_0 *__cself) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
(b->__forwarding->b) = 4;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_1v6qx94n2gl27sx0j1dqlrs40000gn_T_TestBlock_b681a6_mi_2,(b->__forwarding->b));
}
可以看到 __block 修饰的对象 b
重新赋值后, 底层转变为 b->__forwarding->b
__forwarding 作用
__forwarding 存在的意义就是不论在任何位置, 都能够顺利访问同一个 __block 变量
Block 类型
- _NSConcreteStackBlock
- _NSConcreateGlobalBlock
- _NSConcreateMallocBlock
MRC 情况下:
- _NSConcreateGlobalBlock:未截获外部变量的情况
- _NSConcreteStackBlock:引用外部变量,
- _NSConcreateMallocBlock :
[block copy]
或者截获__block
修饰的变量
ARC 情况下
- _NSConcreateGlobalBlock : 未截获外部变量的情况
- _NSConcreateMallocBlock : 变量在赋值的时候自动拷贝到堆区
内存管理
MRC 下, 栈上的 Block 随着作用域结束会被销毁, 堆上的 Block 随着作用域结束不会被释放