古巷悠悠岁月深,青石老街印旧痕
今夜小楼听风雨,不见当年伞下人
前言
Block作为iOS中老生常谈的问题,也是面试中面试官比较喜欢问的 一个问题 ,下面我们通过源码查看block的底层实现原理
什么是Block
Block:将函数及其上下文组装起来的对象
Block本质就是一个对象
创建一个PHJBlock类
@implementation PHJBlock
- (void)test {
int a = 10;
void (^ block)(void) = ^{
NSLog(@"%d", a);
};
block();
}
@end
查看编译后的C++源码
编译: Clang -rewrite-objc PHJBlock.m
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
int a = 10;
void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__PHJBlock__test_block_impl_0的内部实现
struct __PHJBlock__test_block_impl_0 {
struct __block_impl impl;
struct __PHJBlock__test_block_desc_0* Desc;
int a;
__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block_impl的内部实现
看到isa,证明block的本质就是一个对象
struct __block_impl {
void *isa; // 看到isa,证明block的本质就是一个对象
int Flags;
int Reserved;
void *FuncPtr;
};
Block的三种类型
栈Block
堆Block
全局Block
int a = 10;
// 堆block
void(^block)(void) = ^{
NSLog(@"%d", a);
};
block();
NSLog(@"%@", block);
// 全局block
void(^block1)(void) = ^{
};
block1();
NSLog(@"%@", block1);
// 栈block
NSLog(@"%@", ^{
NSLog(@"%d", a);
});
打印查看block内存地址
Block捕获外部变量
局部变量
基本数据类型:截获其值
对象类型:对于对象类型的局部变量连同所有权修饰符一起截获
静态局部变量
以指针形式
全局变量
不截获
静态全局变量
不截获
Block捕获基本数据类型局部变量
不加修饰词修饰的变量
- (void)test {
int a = 100;
void(^block)(void) = ^{
printf("%d", a);
};
block();
}
编译后的C++代码:捕获外部变量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
// 捕获 a 的值
int a = __cself->a; // bound by copy
printf("%d", a);
}
加修饰词修饰的变量
- (void)test {
__block int a = 100;
void(^block)(void) = ^{
a ++;
printf("%d", a);
};
block();
}
编译后的C++代码:捕获外部变量的的地址
总结:这也就解释了为什么我们在外部变量前加上__block就能在block内部可以修改变量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
// 捕获 a 的地址
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) ++;
printf("%d", (a->__forwarding->a));
}
发现变量a加上__block后变成了一个对象
__forwarding指针
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
Block捕获对象类型局部变量
- (void)test {
__unsafe_unretained id obj = nil;
__strong NSObject *obj1 = nil;
void(^block)(void) = ^{
NSLog(@"__unsafe_unretained类型变量:%@", obj);
NSLog(@"__strong类型变量:%@", obj1);
};
block();
}
编译后的C++代码:连同外部变量的修饰词一起捕获
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
// 修饰词一起捕获
__attribute__((objc_ownership(none))) id obj = __null;
__attribute__((objc_ownership(strong))) NSObject *obj1 = __null;
void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
总结:这也就解释了为什么我们在外部对象前加上__weak就能在block内部使用的时候可以避免循环引用问题
Block捕获静态局部变量
- (void)test {
static int a = 100;
void(^block)(void) = ^{
NSLog(@"static类型变量a :%d", a);
};
block();
}
编译后的C++代码:捕获变量的地址
&a代表传入的是a变量的地址
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
static int a = 100;
// 下面的&a代表传入的是a变量的地址
void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
int *a接收传入的a变量的地址
struct __PHJBlock__test_block_impl_0 {
struct __block_impl impl;
struct __PHJBlock__test_block_desc_0* Desc;
// 捕获a变量的地址
int *a;
__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Block不会捕获全局变量
int a = 100;
@implementation PHJBlock
- (void)test {
void(^block)(void) = ^{
NSLog(@"全局变量a :%d", a);
};
block();
}
@end
编译后的C++代码:不会捕获全局变量
struct __PHJBlock__test_block_impl_0 {
struct __block_impl impl;
struct __PHJBlock__test_block_desc_0* Desc;
__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Block不会捕获静态全局变量
static int a = 100;
@implementation PHJBlock
- (void)test {
void(^block)(void) = ^{
NSLog(@"全局变量a :%d", a);
};
block();
}
@end
编译后的C++代码:不会捕获静态全局变量
struct __PHJBlock__test_block_impl_0 {
struct __block_impl impl;
struct __PHJBlock__test_block_desc_0* Desc;
__PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
一般情况下对截获变量进行赋值操作需要加__block
需要加__block
__block NSMutableDictionary *dicM = nil;
void(^block)(void) = ^{
dicM = [NSMutableDictionary dictionary];
};
block();
不需要加__block
NSMutableDictionary *dicM = nil;
void(^block)(void) = ^{
[dicM setObject:@(1) forKey:@"1"];
};
block();
总结:赋值的时候需要加__block,操作使用的时候不用加
__forwarding指针
栈Block没有进行copy操作
栈__forwarding指针都指向栈中自己的变量
栈Block如果进行了copy操作
栈和堆上的__forwarding指针都指向堆的变量
总结:不论在任何内存位置,都可以顺利访问同一个__block变量
Block的Copy操作
栈block进行copy,得到堆block
堆block进行copy,增加引用计数
全局block进行copy,block不会产生影响
__block 修饰局部对象类型变量的循环引用问题
MRC下,不会产生循环引用
在ARC下,会产生循环引用,引起内存泄漏,解决:在block内部对__block修饰的对象变量进行置nil操作