善恶众相

  • 首页

  • 分类

  • 归档

iOS内存管理ARC

发表于 2018-06-08 更新于 2019-10-18 分类于 iOS
Automatic Reference Count深入原理。

编译器和LLVM IR

当代编译器遵循以下架构:前端(词法分析与解析器),优化器,后端

前端就是lexer加上parser,设计目标是把源代码转成数据结构,一般是AST的结构,即Abstract Syntax Tree,用来表示源代码当中的各种代码的层级关系。
接下来看Optimizer,这块的作用是把前端的转化而成的数据结构进行优化,但前提是不改变源代码原本要表达的逻辑。
优化这块可以做的,比如去除掉永远运行不到的代码,把一些写得冗长不合理的逻辑改短,同时不改变逻辑本身,等等。
最后是后端。后端就是把中间的数据结构转化成目标平台的源代码。这部分要用到的技术也是独立的一块。
这样设计编译器的好处是什么?实际上就是前端和后端可以各自独立实现,独立维护。因为前端和后端所用到的技术各自都比较独立,所以对于维护编译器这样复杂的项目来说,模块化是必要的。
还有一个更重要的原因,就是这样的设计可以让一个编译器支持多语言,多平台的设计。类似下图所示:

ARC情况下编译器为我们做了什么

通过以下命令可以将OC源码转换成LLVM IR(中间语言),便于我们对ARC进行分析:

1
clang -S -fobjc-arc -emit-llvm main.m -o main.ll

先简单举个例子:

1
2
3
int main() {
return 0;
}

上面的C源码转换为LLVM IR语言之后:

1
2
3
4
5
define i32 @main() #0 {
%1 = alloca i32, align 4 //声明局部变量%1
store i32 0, i32* %1, align 4 //赋值局部变量%1的值为 0
ret i32 0
}

简单解释:

  1. @代表全局变量,%代表局部变量
  2. %1 = alloca i32, align 4 栈上分配内存
  3. load读出内容 store写入内容
  4. i32 i表示32位integer32,即四字节
  5. align 4 4字节对齐

下面我们通过分析以下几种声明所对应的LLVM IR语言来解读一下,编译器到底针对这几种声明为我们做了什么,从而实现自动管理内存(ARC)的:

__strong

1
2
3
4
int main() {
id a;
return 0;
}

再ARC环境下,声明变量默认添加的是__strong属性,上面源码对应的LLVM IR:

1
2
3
4
5
6
7
8
9
10
define i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i8*, align 8
store i32 0, i32* %1, align 4
store i8* null, i8** %2, align 8
store i32 0, i32* %1, align 4
call void @objc_storeStrong(i8** %2, i8* null) #1
%3 = load i32, i32* %1, align 4
ret i32 %3
}

排除掉干扰之后:

1
2
3
4
5
6
7
8
9
define i32 @main() #0 {
.
%2 = alloca i8*, align 8
.
store i8* null, i8** %2, align 8
.
call void @objc_storeStrong(i8** %2, i8* null) #1
.
}

根据objc_storeStrong的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
//对obj进行了retain,对location进行了release
//如果传入obj==null,则该函数只是对location进行了release操作
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}

我们可以得出:在__strong类型的变量的作用域结束时,自动添加release函数进行释放。

再看一下__strong属性的赋值操作,ARC环境下编译器是如何替我们优化的:

1
2
3
4
5
int main() {
id a;
__strong id b = a;
return 0;
}

对应的LLVM IR:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
define i32 @main() #0 {
.
%2 = alloca i8*, align 8 //__strong id a
%3 = alloca i8*, align 8 //__strong id b
.
store i8* null, i8** %2, align 8 //a = null
%4 = load i8*, i8** %2, align 8
%5 = call i8* @objc_retain(i8* %4) #2// [a retain]
store i8* %5, i8** %3, align 8
.
call void @objc_storeStrong(i8** %3, i8* null) #2// release b
call void @objc_storeStrong(i8** %2, i8* null) #2// release a
.
}

我们看到,在为strong属性的变量b赋值的时候,对a进行了objc_retain操作,当作用域结束时,对两个strong属性的变量a,b都进行release的操作。

__weak

weak指针的实现借助于Objective-C的运行时特性,在启动runtime时基于SideTable结构体,通过objc_initWeak``objc_storeWeak, objc_destroyWeak和objc_moveWeak等方法,直接修改__weak对象,来实现弱引用。
objc_storeWeak函数,将附有__weak标识符的变量的地址注册到weak表中,weak表是一份与引用计数表相似的散列表。
而该变量会在释放的过程中清理weak表中的引用,变量释放调用以下函数:

1
2
3
4
5
6
>dealloc
>_objc_rootDealloc
>object_dispose
>objc_destructInstance
>objc_clear_deallocating
>
1
2
3
在最后的`objc_clear_deallocating`函数中,从weak表中找到弱引用指针的地址,然后置为nil,并从weak表删除记录。

__weak属性的赋值操作,ARC环境下编译器是如何替我们优化的:

int main() {
id a;
__weak id b = a;
return 0;
}

1
对应的LLVM IR:

define i32 @main() #0 {
.
%2 = alloca i8, align 8// __strong id a
%3 = alloca i8
, align 8// __weak id b
.
store i8* null, i8* %2, align 8 //a = null
%4 = load i8
, i8* %2, align 8
%5 = call i8
@objc_initWeak(i8* %3, i8 %4) #1 //objc_initWeak(&b, a)
.
call void @objc_destroyWeak(i8* %3) #1//b = nil置为nil
call void @objc_storeStrong(i8** %2, i8
null) #1// release a
.
}

1
2
3
4
5
6
7
8
在为`weak`对象赋值时,调用`objc_initWeak`函数,而在`weak`对象超过作用域时,使用`objc_destroyWeak`进行释放。

## __autorelease

Autorelease对象是在当前的runloop迭代结束时释放的。
Autorelease的实现是基于AutoreleasePoolPage这样一个双链表结构实现的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池,通过`objc_autoreleasePoolPush`来实现将对象压入池中并做出标记,通过`objc_autoreleasePoolPop`对之对应的标记之后加入到池中对象进行release操作。

ARC下,runtime有一套对autorelease返回值的优化策略:
  • (instancetype)createSark {
    return [self new];
    }
    // caller
    Sark *sark = [Sark createSark];

    1
    2

    编译器改写成了形如下面的代码:
  • (instancetype)createSark {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
    }
    // caller
    id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我们调用retain
    Sark *sark = tmp;
    objc_storeStrong(&sark, nil); // 相当于代替我们调用了release

    1
    2
    3
    4

    Autorelease返回值的快速释放,并非对autorelease对象进行了retain/release操作,而是将通过tls对对象进行了简单的存储(objc_autoreleaseReturnValue)和读取(objc_retainAutoreleasedReturnValue)操作。

    __autorelease属性的赋值操作,ARC环境下编译器是如何替我们优化的:

int main() {
id a;
__autoreleasing id b = a;
return 0;
}

1
对应的LLVM IR:

define i32 @main() #0 {
.
%2 = alloca i8, align 8 // __strong id a
%3 = alloca i8
, align 8 // __autorelease id b
.
store i8* null, i8* %2, align 8 // a = null
%4 = load i8
, i8* %2, align 8
%5 = call i8
@objc_retainAutorelease(i8* %4) #1 // objc_retainAutorelease(a)
store i8* %5, i8* %3, align 8
.
call void @objc_storeStrong(i8** %2, i8
null) #1
.
}

1
2


id objc_retainAutorelease(id value) {
return objc_autorelease(objc_retain(value));
}

1
2
3
4
5
`objc_retainAutorelease`对一个变量先进行一次retain,再添进行autorelease。

## __unsafe_unretained

__unsafe_unretained属性的赋值操作:

int main() {
id a;
__unsafe_unretained id b = a;
return 0;
}

1
对应LLVM IR:

define i32 @main() #0 {
.
%2 = alloca i8, align 8
%3 = alloca i8
, align 8
.
store i8* null, i8* %2, align 8
%4 = load i8
, i8* %2, align 8
store i8
%4, i8* %3, align 8
.
call void @objc_storeStrong(i8** %2, i8
null) #1
.
}

1
2
3
4
5
对于声明为`__unsafe_unretained`属性的对象指针,编译器只是通过store对指针进行赋值,并没有其他相关函数的添加,所以`unsafe_unretained`只是单纯的保存指针,不考虑引用计数相关的内存管理问题。

>当我们直接使用performSelector:执行一个传入的SEL时,编译器会抛出异常

>

performSelector may cause a leak because its selector is unknown

1
2
3
对于函数performSelector:,其返回值是id,对于以下函数:

>

Hello *b;
b = [a performSelector:sel];

1
2
3
```
我们知道b会对performSelector:返回的结果调用retain操作,在b对象离开作用域时进行一次release操作。
而如果selector是以 new,copy,mutableCopy和alloc开头的,则返回的对象是带有一个引用计数的,则在调用函数处进行了一次retain 和release后,该对象还是拥有一个引用计数,在ARC下就发生了内存泄露。
Sqlite3
利用线程和信号量模拟冰淇淋商店运营情况
  • 文章目录
  • 站点概览
Zrongl

Zrongl

23 日志
3 分类
GitHub E-Mail
  1. 1. 编译器和LLVM IR
  2. 2. ARC情况下编译器为我们做了什么
    1. 2.1. __strong
    2. 2.2. __weak
© 2019 Zrongl
不争无尤
|
主题 – NexT.Mist v7.3.0
0%