目录
-
序
-
block实质
-
源码分析
-
自动变量值的截获
-
循环引用问题
-
资料推荐
关于block,我们在开发中经常用到,使用上就不具体阐述了,无外乎是回调传值,但是兄弟在某次面试的时候遇到过这样一道笔试,当时就懵逼了,这也是促使我想了解block底层的原因。
下面四个方法打印的内容分别是什么?
void test1() {
int a = 10;
void (^block)(void) = ^{
NSLog(@"a is %d", a);
};
a = 20; // 10
block();
}
void test2() {
__block int a = 10;
void (^block)(void) = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 20
}
void test3() {
static int a = 10;
void (^block)(void) = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 20
}
int a = 10;
void test4() {
void (^block)(void) = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); //20
}
一、block实质:
1.1、实质
带有自动变量值的匿名函数
1.2、分类
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- NSGlobalBlock (NSConcreteGlobalBlock)
- NSMallocBlock (NSConcreteStackBlock)
- NSMallocBlock (NSConcreteMallocBlock)
二、源码分析
-
2.1、源码转换命令
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
error: cannot create __weak reference because the current deployment target does not support weak references
使用 __weak 解决循环引用的时转换会报错,此时需要加上 runtime 版本
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m
-
2.2、源码分析
创建一个最简单的block(无参无返回值)
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
printf("this is a block");
};
block();
}
return 0;
}
通过上面使用clang生成源码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
// 第一个成员变量impl结构体,结构体为上面的__block_impl
struct __block_impl impl;
// 第二个成员变量是 Desc 指针,对应的结构体为 __main_block_desc_0
struct __main_block_desc_0* Desc;
// 构造函数(类似于OC的init方法)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block执行逻辑函数,也能看出来,这个函数对应代码中的打印
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("this is a block")
}
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升级所对应的区域
size_t Block_size; // block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
- 2.3、函数命名规则
block语法所属的函数名 + 该语法在函数中出现的顺序值
在main函数中实现的第一个block和第二个block
-
2.4、函数解答
首先,我们来看 __main_block_func_0 函数,因为它的标记最明显,含有 block 的执行代码 printf("this is a block"),也就是说源代码中的 block 被转换成了以下函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("this is a block")
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 第一行
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 第二行
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/*
* 上述代码删除强制转换后
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block);
*/
}
return 0;
}
我们先来看一下删除强制转换后的第一行代码,该代码将__main_block_impl_0结构体类型的自动变量,赋值给block。
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_impl_0,
&__main_block_desc_0_DATA);
struct __main_block_impl_0 *block = &tmp;
第二行代码对应源代码中的 block(),去掉转换部分为
(*block -> impl.FuncPtr)(block)
由block转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中
__main_block_desc_0 结构体实例初始化
static struct __main_block_desc_0 __main_block_desc_0_DATE = {
0,
sizeof(struct __main_block_impl_0)
}
- __main_block_impl_0** 构造函数初始化
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
}
三、自动变量值的截获
所谓 截获自动变量值 就是在执行block语法时,语法表达式所使用的自动变量值被保存到block的结构体实例(即Block自身)中;
目的是为了保证block内部能够正常访问外部变量,因为自动变量内存随时可能销毁。
- 3.1、自动变量值截获只能保存执行Block语法的瞬间值,保存后就不能修改
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger age = 10;
void(^block)(void) = ^{
age = 20;
};
block();
}
return 0;
}
error:Variable is not assignable (missing __block type specifier)
3.2、解决不能改写被截获自动变量值的两种方法
- 使用全局变量、静态全局变量、静态变量截获自动变量值
int global_val = 1; // 全局变量
static int static_global_val = 2; // 静态全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_val = 3; // 静态变量
void(^blk)(void) = ^{
global_val *= 2;
static_global_val *= 2;
static_val *= 2;
};
blk();
}
return 0;
}
global_val = 1;
static_global_val = 2;
static_val = 3;
-------------------
global_val = 2;
static_global_val = 4;
static_val = 6;
源码分析
- 使用__block说明符截获自动变量值 (编译器会将__block变量包装成一个对象)
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int var = 10;
void(^blk)(void) = ^{
var = 1;
};
blk();
}
return 0;
}
__block源码分析
// 编译器会将来__block变量包装成一个对象
struct __Block_byref_var_0 {
void *__isa;
// 自己类型的指针,指向自己,用于查找自己的结构体地址,找到变量(修改)
__Block_byref_var_0 *__forwarding;
int __flags;
int __size;
int var; // 10 => 20 该结构体持有相当于原自动变量的成员变量
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_var_0 *var; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_var_0 *var = __cself->var; // bound by ref
(var->__forwarding->var) = 1; // 通过__forwarding拿到变量
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};
/*
__Block_byref_var_0 var = {
0,
&var, // 赋值给__forwarding
0,
sizeof(__Block_byref_var_0),
10
};
*/
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
/*
void(*blk)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_var_0 *)&var,
570425344)
);
*/
}
return 0;
}
3.4、问题解析
- 截获Object-C对象,调用变更该对象的方法也会产生编译错误么?
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *arrayM = [[NSMutableArray alloc] init];
void(^block)(void) = ^{
[arrayM addObject@"123"];
};
block();
}
return 0;
}
Program ended with exit code: 0
为什么这样就不报错了呢?如果用C语言描述,即是截获了NSMutableArray类对象用的结构体实例指针,
虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。
同理,如果像截获的变量array赋值则会产生编译错误
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *arrayM = [[NSMutableArray alloc] init];
void(^block)(void) = ^{
arrayM = [[NSMutableArray alloc] init];
};
block();
}
return 0;
}
Variable is not assignable (missing __block type specifier)
- 使用C语言数组时,必须小心使用其指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
const char text1[] = "hello";
void(^block)(void) = ^{
printf("%c", text1[1]);
};
block();
}
return 0;
}
error:Cannot refer to declaration with an array type inside block
只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有任何问题,但实际会产生编译错误。
这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。此时,使用指针可以解决该问题
int main(int argc, const char * argv[]) {
@autoreleasepool {
const char *text1 = "hello";
void(^block)(void) = ^{
printf("%c", text1[1]);
};
block();
}
return 0;
}
四、循环引用问题
在block中,如果使用附有__strong修饰的对象,当block从栈复制到堆时,该对象为block所持有,也就是我们说的循环引用
typedef void(^blk_t)(void);
@interface MyObject : NSObject
@property (nonatomic, copy) blk_t blk;
@end
@implementation MyObject
- (instancetype)init {
if (self = [super init]) {
_blk = ^{
NSLog(@"self = %@", self);
};
_blk()
}
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyObject *object = [[MyObject alloc] init];
}
return 0;
}
2019-08-19 21:12:59.677199+0800 test[741:11435] self = <MyObject: 0x100631cc0>
Program ended with exit code: 0
这里可以看到,dealloc 方法没有调用,也就是说,当方法超出作用域之后 MyObject 没有被销毁,也就是产生了内存泄露。
循环引用代码内存图对应代码为
ATmWgJxT19_07_19__08_22_2019.jpg
AUNuEU8N19_07_54__08_22_2019.jpg 当执行完作用域之后,object对象被销毁 相互持有 循环引用原理
打破循环引用
- 4.1、使用__weak打破循环
- (instancetype)init {
if (self = [super init]) {
id __weak weakSekf = self;
_blk = ^{
NSLog(@"self = %@", weakSekf);
};
}
return self;
}
__weak 指向的对象销毁时,会自动让指针置为nil,也是最推荐的解决 block 循环引用一种。
dhdVdjJ21_46_58__08_19_2019.jpgAm0gxDuw19_24_35__08_22_2019.jpg
- 4.2、使用__block解决
__block可以用于解决block内部无法修改auto变量值的问题;
__block不能修饰全局变量、静态变量;
- (instancetype)init {
if (self = [super init]) {
__block id blockSelf = self;
_blk = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil;
};
_blk(); // 使用__block解决循环引用,必须调用block
}
return self;
}
使用 __block 解决循环引用,必须调用 block,并且将 block 置为nil
blockSelf = nil;
Aipqitv20_12_16__08_11_2019.jpg
- 4.3、使用__unsafe_unretained解决
- (instancetype)init {
if (self = [super init]) {
__unsafe_unretained MyObject *mySelf = self;
_blk = ^{
NSLog(@"self = %@", mySelf);
};
_blk();
}
return self;
}
使用__unsafe_unretained是不安全的,因为它指向的对象销毁时,指针存储的地址值不变,这样的话可能造成野指针。
五、资料推荐
如果觉得我总结的很一般,推荐三个资料,用心看完之后对 block 底层的认识能提升很多, 《Objective-C高级编程 iOS与OS X多线程和内存管理》啃起来费劲的话,建议先看视频,感觉李明杰讲 block 的时候是以这本书为教材的,不过底层就这些东西,无所谓谁先谁后的,学到知识是自己的
- 《Effective Objective-C 2.0》
第6章 块于大中枢派发
- 《底层原理下 - 李明杰视频》
day8 - day10
- 《Objective-C高级编程 iOS与OS X多线程和内存管理》
第2章 Blocks
网友评论