Swift Memory Management

ObjC和Swift都是使用 llvm 来进行编译,llvm 没有 GC,但 Apple 在这两门语言中添加了 ARC 自动内存回收 来安全,有效的进行内存管理

引用计数

引用计数指的是每一个变量都有一个引用计数,它用于说明有多少其它对象、变量依赖于该变量。当计数值为0时,表示该变量不再被任何对象、变量依赖,可立即被回收,从内存中销毁。

MRC 手动引用计数

在早期的ObjC语言开发中,内存管理和C是一致是,要求开发者在申请内存后,手动的进行清除。

1
NSArray *newArray = [[[NSArray alloc] init] autorelease];

ARC 自动引用计数

ARC != GC

首先,ARC 并不是 Java, C# 中提到的内存自动回收 GC 技术,开发者只要声明各种引用的类型,编译器会在编译前在合适的位置填补引用管理代码,也就是 autorelease, retain, release标记

Swift 中只支持 ARC。

相对于 GC 定期进行检查清理,ARC 的好处就是资源消耗少。

引用类型

  • retain
  • strong
  • weak
  • assgin

作用域

通过这段代码,可以看到,引用和作用域的关系:

1
2
3
4
5
6
7
8
9
class bottle {}

func play(count: Int) {
var left = Bottle() // 标注1
if (count > 1) {
var right = Bottle() // 标注2
right = left // 标注1的变量,计数器为2,标注2的变量,计数器为0,立即回收
} // 变量 right 作用域结束,标注1变量计数器为1
} // 变量 left 作用域结束,标注1变量计数器为0,回收。

Ownership 所有权

1
2
3
4
5
6
7
class Apartment {
var person: Person
}

class Person {
var home: Apartment
}

变量声明默认都是强引用,其计数器+1。。

在上面示例中,ApartmentPerson 产生了循环依赖,也就是计数器永远都不会为0,内存也就永远不会被销毁,从而引发内存泄漏。

需要使用弱/非持有引用,它们的区别:

  • 弱引用可以为空
  • 非持有不能为空。

在经常使用的 ViewController,VC1 中 Push VC2,VC2如果要调用VC1的方法,就可以使用非持有引用。因为它是反方向。

- Weak References 弱引用

和 ObjC 中的 weak 一样,不会增加引用计数。

弱引用一定是可能值,它不增加计数器值,当该变量其它所有强类型引用都被销毁,该变量也会变回收。

1
2
3
4
5
6
7
class Apartment {
weak var person: Person?
}

class Person {
weak var home: Apartment?
}

在可能值绑定时,会产生 强引用

1
2
3
4
5
6
if let person = apt.person {
person.xxx // person 为强引用
} // 作用域结束,person 销毁

// 可能值链也会产生强引用,但作用域就该行。
apt.person?.xxx()

检测时,不会产生强引用

1
2
3
if apt.person != nil {
apt.person!.xxx() // 这种写法有问题,判断时有值,但这里不一定有值,会引发crash。
}

- 非持有引用 Unowned References

有一些业务模式中,会有同生命线的关系 Same-Lifetime Relationships

在信用卡和人的示例中,业务要求:

  • 人或许有一张信用卡
  • 信用卡肯定会有一个持有者
  • 信用卡持有者不会发生改变
  • 持有者不在,信用卡也会跟着销毁
1
2
3
4
5
6
7
8
9
10
11
class Person {
var card: CreditCard?
}

class CreditCard {
let holder: Person // 信用卡一定有持有者,则不会变

init(holder: Person) {
self.holder = holder
}
}

人指向信用卡,信用卡也指向人。会产生内存泄漏。

如果将holder设为weak也就是:

1
2
3
4
5
weak var holder: Person? 
// weak 一定是 Optional 类型
// Optional 类型值可能会改变,所以不能用let,只能用var。

// 就不满足卡一定有持有者(?)、持有者不在,信用卡也会消亡的规则的业务属性了(var)。

系统提供了新的关健字 unowned,它表示一定有值,系统不会将该变量设置为nil。

1
2
3
class CreditCard {
unowned let holder: Person
}

非持有引用,可以正常的使用,不需要拆包:

1
2
let holder = card.holder
holder.charge()

- 引用使用规则

  • 所有的正向引用情况,都是强引用。
  • 对于不同生命线的对象之间关系,使用弱引用。
  • 同生命线反向引用使用非持有引用。

REF::