Swift4语法极简手册 — ARC

Swift4语法极简手册

第十六章节:ARC

Swift使用ARC来管理内存,大多数情况下,你不需要知道或关心这些细节,但对于一些特殊的情况,要知道如何处理

ARC如何工作

  • 当你创建一个实例,Swift分配一块内存来存储它
  • 如果实例已经不被需要,Swift会释放这块内存
  • 如果你访问一个已释放内存的实例,App会闪退
  • Swfit会跟踪纪录有多少个properties,constans,variables引用它,每个引用计为加1;这些引用称为strong reference
  • 当properties,constants或variables不再引用此对象时,引用计数减1
  • 如果一个实例的引用数为0时,Swift翻译内存
1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
init() {
print("初始化了")
}
deinit {
print("我被释放了")
}
}
//A()是一个新的实例,因为被变量引用,引用+1
var a:A? = A()
//a不再指向A(),A()被释放,引用 -1,变为0,内存释放,执行deinit方法
a = nil

如上代码所示:A()会分配一块新的内存,然后变量a指向它,于是引用加1

a不再引用它时,引用减1变为0,于是内存被释放

ARC就是这样管理内存的,大部分情况下是完全OK的,你不需要太关心它实现的细节

循环引用

当然我们肯定有例外,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var person:Person? = Person(name: "lingen")
var apartment:Apartment? = Apartment(unit: "China")
person!.apartment = apartment
apartment!.tenant = person
person = nil
apartment = nil
//两个实例的deinit都不会执行

WHY

这就是循环引用

  • 当person为nil的时候,Person(name: “lingen”)不会释放内存,因为apartment!.tenant = person还持有对这块内存的引用
  • 同样,当apartment为nil的时候,Apartment(unit: “China”)也不会释放,因为person!.apartment = apartment还持有对这块内存的引用

解决循环引用的两种方式

  1. Weak Reference
  2. Unowned Reference

Weak Reference

Weak Reference是弱引用,区别于Strong Reference,也就是这种引用的引用数不会加1

如前所述的PersonApartment这种相互引用就适合使用weak

Weak一般定义在持有生命周期较短的一方的实例中,在这里是Apartment,因为Apartment可能会有人住,也可能会没有人住,所以将Apartment中的Person引用为weak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
//用weak关键字引用为弱引用
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var person:Person? = Person(name: "lingen")
var apartment:Apartment? = Apartment(unit: "China")
person!.apartment = apartment
apartment!.tenant = person
person = nil
apartment = nil
//output
//lingen is being deinitialized
//Apartment China is being deinitialized

apartment!.tenant = person并不会对person的引用数加1,因为它是weak定义的,不是strong引用

weak引用的变量,一定要是Optional的,因为一旦引用的实例被内存回收,此变量值会变为nil

Unowned References

与weak一样,unowned也是弱引用,unowned适合以下情况

  1. 当引用一个对象,明确知道对方不会为空,而对方又引用自己时
  2. unowned定义一定是非Optional的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
//unowned引用,弱引用
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}

如上述代码所示:用户和银行卡的关系,一个用户可能会有银行卡,一个银行卡一定会所属一个用户

  1. 用户引用了银行卡,并且未定义为weak,是强引用
  2. 银行卡也引用了用户,由于有银行卡就有用户,使用weak不适合,所以使用unowned更为合适

是weak还是owned

如上代码所述,如果在Customer将CreditCard定义为weak也是可以的,那如果选择weak还是unowned

苹果建议,如果你能明确引用的不可能为nil,那使用unowned是比较恰当的,否则考虑使用weak

Closures中的ARC

闭包可能被Class的引用,然后闭包也会引用Class中的self或其它,这种情况同样会造成循环引用,同样也可以使用weakunowned来解决这个问题

1
2
3
4
var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}

1
2
3
4
5
6
7
8
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}

使用[unowned self][unowned self, weak delegate = self.delegate!]来使闭包弱引用类中的对象