iOS block原理详解
source link: https://gsl201600.github.io/2020/05/13/iOSblock%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
iOS block原理详解
- block本质
block
底层就是一个struct __main_block_impl_0
类型的结构体
,这个结构体中包含一个isa
指针,本质上是一个OC
对象block
是封装了函数调用
以及函数调用环境
的OC
对象
block底层结构
block
底层结构就是__main_block_impl_0
结构体,内部包含了impl结构体
和Desc结构体
以及外部需要访问的变量
,block
将需要执行的代码放到一个函数里,impl
内部的FuncPtr
指向这个函数的地址,通过地址调用这个函数,就可以执行block
里面的代码了。Desc
用来描述block
,内部的reserved
作保留,Block_size
描述block
占用内存block的变量捕获
局部变量block
访问方式是值传递
,auto自动变量
可能会销毁,内存可能会消失,不采用指针访问;局部静态变量block
访问方式是指针传递
,static变量
一直保存在内存中,指针访问即可;全局变量、静态全局变量block
不需要对变量捕获,直接取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// block的变量捕获代码解析如下
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d,height is %d",age,height);
};
age = 20;
height = 20;
block();
-------------------------------------------------
output: age is 10,height is 20
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 值传递
int *height; // 指针传递
}
- block的类型
__NSGlobalBlock__
没有访问auto变量
数据区
什么也不做,类型不改变
__NSStackBlock__
访问了auto变量
栈区
从栈复制到堆,类型改变为__NSMallocBlock__
__NSMallocBlock__
__NSStackBlock__
调用了copy
堆区
引用计数+1
,类型不改变
在ARC
下Block
访问auto变量
时系统默认帮我们进行了copy
操作,NSGlobalBlock
访问了auto变量
时会变成NSStackBlock
,当NSStackBlock
进行copy
操作后会变成NSMallocBlock
- 在
ARC
环境下,编译器会根据以下几种情况自动将栈上的block
复制到堆上
:
1、block
作为函数返回值时,比如使用=
2、将block
赋值给__strong
指针时
3、block
作为Cocoa API
中方法名含有usingBlock
的方法参数时
4、block
作为GCD API
的方法参数时
- 对象类型的auto变量
- 当
block
内部访问了对象类型的auto变量
时:
如果block在栈空间
,不论是ARC还是MRC
环境,不管外部变量
是强引用还是弱引用
,block
都会弱引用
访问对象
如果block在堆空间
,如果外部强引用
,block
内部也是强引用
;如果外部弱引用
,block
内部也是弱引用
- 栈block:
a) 如果block
是在栈上
,将不会对auto变量
产生强引用
b)栈上的block
随时会被销毁,也没必要去强引用其他对象 - 堆block:
- *1、如果block被拷贝到堆上**
a) 会调用block
内部的copy
函数
b)copy
函数内部会调用_Block_object_assign
函数
c)_Block_object_assign
函数会根据auto变量
的修饰符__strong
、__weak
、__unsafe_unretained
做出相应的操作,形成强引用
或者弱引用
- *2、如果block从堆上移除**
a) 会调用block
内部的dispose
函数
b)dispose
函数内部会调用_Block_object_dispose
函数
c)_Block_object_dispose
函数会自动释放引用的auto变量
(release
,引用计数-1
,若为0
,则销毁)
- __block
__block
修饰符作用:__block
可以用于解决block
内部无法修改auto变量值
的问题__block
不能修饰全局变量、静态变量static
__block
修饰符原理:
编译器会将__block
变量包装成一个结构体__Block_byref_age_0
,结构体内部*__forwarding
是指向自身的指针,内部还存储着外部auto变量
的值__block
的forwarding
指针如下图:
栈上
,__block
结构体中的__forwarding
指针指向自己
,一旦复制到堆上
,栈上的__block
结构体中的__forwarding
指针会指向堆上的__block
结构体,堆上__block
结构体中的__forwarding
还是指向自己
。假设age
是栈上
的变量,age->__forwarding
会拿到堆上的__block
结构体,age->__forwarding->age
会把20
赋值到堆上
,不论是栈上还是堆上的__block
结构体,都能保证20
赋值到堆的结构体
里
- 思考题:block修改NSMutableString、NSMutableArray、NSMutableDictionary,需不需要添加__block
题目如下:以下代码是否可以正确执行1
2
3
4
5
6
7
8
9
10
11
12int main(int argc, const char * argv[]) {
分析:可以正确执行,因为在
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
[array addObject: @"5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();
}
return 0;
}block
块中仅仅是使用了array的内存地址
,往内存地址
中添加内容
,并没有修改arry的内存地址
,因此array
不需要使用__block修饰
也可以正确编译。当仅仅是使用局部变量的内存地址
,而不是修改
的时候,尽量不要添加__block
,通过上述分析我们知道一旦添加了__block
修饰符,系统会自动创建相应的结构体,占用不必要的内存空间
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK