Swift-自动引用计数(ARC)

Swift内存管理:管理引用类型的内存,不会管理值类型,值类型不需要管理。

内存管理原则:当没有任何强引用指向对象,系统会自动销毁对象(默认情况下所有的引用都是强引用)

通常情况下我们不需要去手动释放内存,因为ARC会在对象不再被引用,会自动销毁对象,但是有些时候还是需要在代码中实现内存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
var name:String
init(name:String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}

var person:Person? = Person(name: "daisuke")
person = nil
//init
//deinit

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
27
class Person {
var name:String
init(name:String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}

// 强引用,引用计数+1
var strongPerson1 = Person(name: "daisuke")
// 引用计数+1 = 2
var strongPerson2 = strongPerson1

// 弱引用,引用计数不变
// 如果利用weak修饰变量,当对象释放后会自动将变量社会为nil
// 所以利用weak修饰的变量必定是一个可选值,因为只有可选类型才能设置为nil
// 'weak' variable should have optional type 'Person?'
weak var weakPerson1:Person? = Person(name: "daisuke")
if let person = weakPerson1{
print(person)
} else {
print("weakPerson1 is nil")
}
// weakPerson1 is nil

unowned无主引用

unowned无主引用,相当于OC unsafe_unretained

unowned和weak的区别:

  1. 利用unowned修饰的变量,对象释放后不会设置为nil,不安全。利用weak修饰的变量,对象释放后会设置nil
  2. 利用unowned修饰的变量,不是可选类型。利用weak修饰的变量,是可选类型
1
2
3
4
5
6
7
8
9
10
11
class Person {
var name:String
init(name:String) {
self.name = name
print("init")
}
deinit {
print("deinit")
}
}
unowned var unownedPerson:Person = Person(name: "daisuke")

weak&unowned使用场景

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
27
28
29
30
31
32
33
34
35
36
37
38
class Apartment {
let number:Int // 房间号
var tenant:Person1? // 租客
init(number:Int) {
self.number = number
}
deinit {
print("\(self.number) deinit")
}
}

class Person1 {
let name:String // 姓名
weak var apartment:Apartment? // 公寓
init(name:String) {
self.name = name
}
deinit {
print("\(self.name) deinit")
}
}

var person1:Person1? = Person1(name: "daisuke")
var apartment:Apartment? = Apartment(number: 888)

// 人有一套公寓
person1?.apartment = apartment
// 公寓中住着一个人
apartment?.tenant = person1

person1 = nil
apartment = nil
// 两个对象没有被销毁,内存泄漏

// 在Person1类中apartment属性前添加weak修饰:weak var apartment:Apartment?
// 那么两个对象就被销毁
//888 deinit
//daisuke deinit

unowned

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
27
28
29
30
31
32
33
34
35
36
class Card {
let number:Int
// 信用卡必须有所属用户
// 当某一个变量/常量必须优质,一直有值,那么可以使用unowned修饰
unowned let person:Person2
init(number:Int, person:Person2) {
self.number = number
self.person = person
}
deinit {
print("\(self.number) deinit")
}
}

class Person2 {
let name:String // 姓名
// 人不一定有信用卡,可选值
var card:Card?
init(name:String) {
self.name = name
}
deinit {
print("\(self.name) deinit")
}
}

var person2:Person2? = Person2(name: "daisuke")
// Value of optional type 'Person2?' not unwrapped; did you mean to use '!' or '?'?
// 因为Card宏person是unowned修饰的,所以必须有值,那么参数person2就不能使用可选值?了,使用强制解包!
//var card:Card? = Card(number: 999, person: person2?)
var card:Card? = Card(number: 999, person: person2!)

person2 = nil
card = nil
//daisuke deinit
//999 deinit

那么总结一下:

  • 使用weak和unowned都可以避免循环引用
  • 使用weak修饰变量,对象被释放后自动设置为nil,安全
  • 使用unowned修饰变量,对象被释放后不能设置nil,因为不是可选类型不能设置nil,不安全
  • 使用unowned修饰的变量,一定要有值,所以当场景需要变量必须有值得时候,使用unowned修饰
  • 使用weak修饰变量,是可选类型,可有值可没有值

闭包引起的循环引用

循环引用:当一个闭包赋值给对象的某个属性,并且这个闭包体中又使用了对象。这个闭包体可能访问了对象的属性,例如:self.someProperty,或者闭包中调用了对象的方法,例如:self.someMethod。这两种情况都导致闭包==捕获==self,从而引起循环引用。

Swift中的闭包循环引用相当于在OC中的block中使用self。

回顾OC中的block循环引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 引起循环引用
self.blk = ^{
NSLog(@"In Block : %@",self.name);
};

// __block声明弱引用
__block XXX* weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf.name);
};

// __weak声明弱引用
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf.name);
};

那么Swift呢?

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Person3 {
let name:String
let text:String?
lazy var testUnownedBlock: () ->String = {
// 加上这句就不会循环引用
[unowned self] in
if let text = self.text{
return text + " is unowned"
} else {
return "text is nil"
}
}

lazy var testWeakBlock: () ->String = {
// 加上这句就不会循环引用
[weak self] in
if let text = self?.text{
return text + " is weak"
} else {
return "text is nil"
}
}

func testWeakBlockFunc() {
weak var weakSelf = self
loadData { (text) in
print("这是block,参数是:\(text), 名称是:\(String(describing: weakSelf?.name))")
}
}

func loadData(finishBlock: (_ text:String) ->()) {
print("loadData is text")
finishBlock("执行block")
}

init(name:String, text:String) {
self.name = name
self.text = text
}
deinit {
print("\(self.name) deinit")
}
}

var person3:Person3? = Person3(name: "daisuke", text: "hello")
//print("text = \(person3!.testUnownedBlock())") // text = hello is unowned
//print("text = \(person3!.testWeakBlock())") // text = hello is weak

person3?.testWeakBlockFunc()
//loadData is text
//这是block,参数是:执行block, 名称是:daisuke)

person3 = nil
// 发现没有调用deinit,所以person3并没有释放。原因是闭包中捕获了self,引起循环引用
// 现在使用unowned修饰或者weak修饰
// 打印:daisuke deinit

总结:

  • 当闭包和捕获的对象总是同时互相引用,并且总是同时销毁的,将闭包内的捕获定义为无主引用unowned
  • 相反,当捕获引用是可能会是nil,将闭包内的捕获定义为弱引用weak
  • 如果捕获的引用绝对不会置nil,应该使用wnowned,而不是weak

参考: