iOS程序猿程序员iOS

Block底层解密

2018-09-22  本文已影响71人  SunshineBrother

Block底层解密

block想必做过一段iOS开发的同学都用过吧,但是大部分人都是仅仅会用,不怎么理解他是怎么实现的,今天就让我们来一步一步的分析一下底层是怎么实现的吧。

查看源码

void (^block)(void) =  ^(){
NSLog(@"this is a block!");
};

这样一个简单的block块大家都应该知道吧,但是这个block块是怎么实现的呢?

想要了解OC对象主要是基于C/C++的什么数据结构实现的,我们首先要做的就是将Object-C代码转化为C/C++代码,这样我们才能清楚的看清是怎么实现的

然后我们打开终端,在命令行找到cd到文件目录,然后中输入:

xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc main.m

执行结束以后,会生成main.cpp文件,我们打开main.cpp文件,拉到最下边就是我们的main函数实现的。

我们得到c++代码的block实现

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

我们知道(void *)这种类型的都是类型的强制转换,为了更好的识别我们的这个Block代码,我们把类型转化去掉

void (*block)(void) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA));

我们在分别查询__main_block_impl_0,__main_block_func_0,__main_block_desc_0_DATA代表什么意思

__main_block_impl_0

struct __main_block_impl_0 {
struct __block_impl impl;
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_impl里面是什么

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

__main_block_func_0

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}

__main_block_desc_0_DATA

static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;//内存大小描述
} __main_block_desc_0_DATA

所以我们可以总结

__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方法

void (*block)(void) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA));

对应上面的初始化我们可以看出第一个参数传递的是执行方法,第二个参数为描述信息

Block底层结构图

Block1.png

成员变量的捕获

为了保证block内部能够正常的访问外部变量,block有个变量捕获机制,这里我们先说结果,然后在进行证明


Block2.png

我们在main函数写下这些代码,然后在把main函数生成c++代码

#import <Foundation/Foundation.h>
int height = 180;
int main(int argc, const char * argv[]) {
@autoreleasepool {


int age = 10;
static int weight = 65;
void (^block)(void) =  ^(){
NSLog(@"age---------%d",age);
NSLog(@"weight---------%d",weight);
NSLog(@"height---------%d",height);
};
block();
}
return 0;
}

我们直接找到c++代码里面存放变量的结构体__main_block_impl_0

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *weight;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

我们可以看到变量捕获为age,*weight,但是没有捕获到全局变量height。为了方便的理解,我们先来了解一些内存空间的分配。

总结:

Block类型

block有3种类型,可以通过调用class方法或者isa指针查看具体的类型,但是最终都是继承者NSBlock类型

她们的内存分配


Block3.png

每一种类型的Block调用copy后的结果

void (^block1)(void) =  ^(){
NSLog(@"block1");
};
int age = 10;
void (^block2)(void) =  ^(){
NSLog(@"block2");
NSLog(@"age---------%d",age);
};
void (^block3)(void) = [ ^(){
NSLog(@"block3");
NSLog(@"age---------%d",age);
} copy];

NSLog(@"block1:%@---->block2:%@----->block3:%@",[block1 class],[block2 class],[block3 class]);

打印结果为

Block4.png

为什么block2打印类型为__NSMallocBlock__,而不是__NSStackBlock__,因为ARC环境导致了,ARC会自动帮我们copy了一下__NSStackBlock__

auto变量修饰符__weak

在开始之前,我先说一下结论,然后我们在去印证

下面我们把ARC环境变成MRC环境,同时稍微修改一下代码,我们在看看dealloc什么时候打印
选择项目 Target -> Build Sttings -> All -> 搜索‘automatic’ -> 把 Objective-C Automatic Reference Counting 设置为 NO

我们写一个Person类,在MRC环境,重写dealloc方法

- (void)dealloc{
[super dealloc];
NSLog(@"person--->dealloc");
}

我们在main函数里面写下这个方法

{
Person *p = [[Person alloc] init];
[p release];
}
NSLog(@"--------");

我们肯定都知道打印结果吧:先打印person--->dealloc,然后打印--------

如果我们添加一个Block呢,

typedef void (^Block)(void);

Block block;
{
Person *p = [[Person alloc] init];
block = ^{
NSLog(@"%@",p);
};
[p release];
}
block();
NSLog(@"--------");

打印结果为

Block5.png

在ARC环境下,代码稍微的改变一下

Block block;
{
Person *p = [[Person alloc] init];
block = ^{
NSLog(@"%@",p);
};

}
block();
NSLog(@"--------");

打印结果为


Block6.png

注意打印顺序:

当Block内部访问了auto变量时,如果block是在栈上,将不会对auto变量产生强引用,因为当Block在栈上的时候,他自己都不能保证自己什么时候被释放,所以block也就不会对自动变量进行强引用了

在ARC环境下如果我们对自动变量进行一些修饰符,那么block对auto变量是进行怎么引用呢
我们还是老方法,把main文件转化为c++文件,我们找到__main_block_func_0执行函数,

Block会自动copy自动变量的修饰属性

__Block修饰

我们都知道想要修改Block外边的变量,我们都会用__Block来修饰自动变量,但是为什么使用__Block修饰就可以在Block内部来更改自动变量了呢。

我们先写一小段代码

__block int age = 10;
NSLog(@"block前age地址1:%p",&age);
Block block = ^{
age = 20;
NSLog(@"block内%d-->age地址2:%p",age,&age);
};
block();
NSLog(@"block后%d-->age地址3:%p",age,&age);

打印结果为

Block7.png
根据内存地址变化可见,__block所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。

我们把main函数转化为C++代码,然后在age使用__Block前后,对Block结构体进行分析

Block8.png

__Block所起到的作用就是只要观察到该变量被 block 所持有之后,age其实变成了OC对象,里面含有isa指针

__Block的内存管理原则

Block9.png Block10.png Block11.png

我们先看到了结果,这里我们在来分析一下源码
__block int age = 10;转化为C++代码,会变成这样

 __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
 
 //为了便于观察,我们可以将强制转化去掉
 __Block_byref_age_0 age = {
 0,
 &age,
 0,
 sizeof(__Block_byref_age_0),
 10};
 

唯一我们不太清除的就是__Block_byref_age_0了,我们查找一下发现

typedef void (*Block)(void);
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};

然后我们在来查找Block实现代码

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref

(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9qtf99yd2qlbx2m97hdjf2yr0000gn_T_main_1757f5_mi_0,(age->__forwarding->age));
}

我们来查看一下age是怎么变成20的(age->__forwarding->age) = 20;,先是找到__forwarding结构体,然后在找到结构提里面的age

总结

解决循环引用

我们看了那么长时间的源码了,一定还记得在auto变量为OC对象的时候,在没有修饰符修饰的时候Block内部会强引用OC对象,而对象如果也持有Block的时候就会造成相互引用,也就是循环引用的问题。

Block12.png

我们也只能在Block持有OC对象的时候,给OC对象添加弱引用修饰符才比较合适,有两个弱引用修饰符__weak__unsafe_unretained

其实还有一种解决方法,那就是使用__Block,需要在Block内部吧OC对象设置为nil

Block13.png
__block id weakSelf = self;
self.block = ^{
weakSelf = nil;
}
self.block();

使用__Block解决必须调用Block

上一篇下一篇

猜你喜欢

热点阅读