扒一扒swift中的unowned和weak上
最近项目中踩到了Closure使用unowned和weak修饰self对象避免循环引用所带来的坑,之前不是太了解,Closure里面会用到self的地方,一般都是这样处理
{[unowned self] in }
但是在一个网络请求的Closure回调里面,这样写总是会令程序crash掉,后来发现在这个Closure执行之前,self已经被释放了,在我们老大查阅了许多资料后把代码改成这样就ok了
{[weak self] in
if let weSelf = self{
执行代码
}
}
最近跟着泊学网(被誉为最良心的swift视频教学网站)的Mars讲师学习了关于swift中Reference cycle引起的内存泄漏方面的知识,总结一下,留着学习!~
那么,一起来扒一扒swift中unwoned和weak之间的恩恩怨怨吧~
首先,想要弄明白unwoned和weak的区别,就一定要先明白他们能做什么,所以,我们就从Reference cycle引起的内存泄露说起吧!
大家都知道,swift会使用ARC(Automatic Reference Counting)帮我们打理内存,但是在有些时候,尤其是对象间存在互相引用的时候,就会由于reference cycle导致内存无法释放,而我们也需要时刻保持清醒,来避免内存泄露。那么
ARC是如何工作的呢?
Swift使用“引用计数(reference count)”来管理类对象的生命周期,避免类对象在“仍被使用”的时候被意外释放。为了观察reference count、对象构建和对象释放之间的关系,我们先来定义一个类:
class Person {
let name: String
init(name: String) {
self.name = name print("\(name) is being initialized.")
}
deinit {
print("\(name) is being deinitialized.")
}
}
接下来,我们观察下面代码的的执行过程:
var ref1: Person?
var ref2: Person?
// Mars is being initialized.
// count = 1
ref1 = Person(name: "Mars")
// count = 2
ref2 = ref1
// count = 1
ref2 = nil
// count = 0
// Mars is being deinitialized.
ref1 = nil
- Person构造时,init被调用,只有一个变量引用了这个对象,此时的reference count为1;
- 当我们把ref1赋值给ref2时,它们指向相同的对象,此时的reference count为2;
- 当ref2为nil时,此时的reference count恢复为1;
- 当ref1为nil时,reference count为0,Tennat的deinit被调用,对象被释放;
在上面的例子里,由于ref1和ref2都会导致Person的reference count加1,因此它们叫做strong reference。这是Swift中的默认行为。
注意: “和带有Garbage Collection的语言不同,当一个对象的reference count为0时,Swift会立即删除该对象。”
那么Reference cycle(循环引用)又是怎么发生的呢?
对于Person这样的单个对象,ARC可以很好的默默为我们工作。但是,当不同类对象之间存在相互引用时
,指向彼此的strong reference就会导致reference cycle。ARC无法释放它们之中的任何一个。来看一个例子,我们给Person添加一个Apartment:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name print("\(name) is being initialized.")
}
deinit {
print("\(name) is being deinitialized.")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit print("Apartment \(unit) is being initialized.")
}
deinit {
print("Apartment \(unit) is being deinitialized.")
}
}
由于Person不一定会租Apartment(公寓),Apartment(公寓)也不一定有房客,因此,Person.apartment和Apartment.tenant都是一个Optional,它们可以为nil。
接下来,我们分别创建一个Person和Apartment对象,然后把变量设置为nil,就可以看到Person和Apartment被构建和销毁了。
// Mars is being initialized
// count = 1
var mars: Person? = Person(name: "Mars")
// Apartment 11 is being initialized
// count = 1
var apt11: Apartment? = Apartment(unit: "11")
// count = 0
// Mars is being deinitializedmars = nil
// count = 0
// apartment is being deinitializedapt11 = nil

接下来,如果我们让mars和apartment分别指向彼此:
// ... Create mars and apartment
// apartment.count = 2
mars!.apartment = apt11
// mars.count = 2
apt11!.tenant = mars
// ... Set mars and apartment to nil
mars = nil
apt11 = nil

这时,尽管我们把mars和apt11设置为nil,Person和Apartmetn的deinit也不会被调用了。因为它们的两个member(apartment和tenant)是一个strong reference,指向了彼此,让对象仍旧“存活”在内存里。但是,mars和apt11已经被设置成nil,我们也已经无能为力了。这就是类对象之间的reference cycle。
三种Reference cycle的解决方案
**1. class member允许为nil时 **
在我们之前的例子里,Apartment和Person中引起reference cycle的数据成员都允许为nil,对于这种情况,我们可以像下面这样使用weak reference来解决reference cycle:
weak var name: Type
对于一个weak reference来说:
- 当其指向类对象时,并不会添加类对象的引用计数;
- 当其指向的类对象不存在时,ARC会自动把weak reference设置为nil;
注意: * “由于weak reference的特定,它只能被定义成var。”*
了解了weak reference特性之后,我们可以把Apartment修改成这样:
class Apartment {
let unit: String
weak var tenant: Person?
// omit for simplicity...
}
var mars: Person? = Person(name: "Mars")
var apt11: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt11
apt11!.tenant = mars
mars = nil
apt11 = nil
mars设置为nil的时候,Apartment和Person的关系就变成了这样:

由于已经没有strong reference指向mars,于是mars就被ARC释放了。接下来,我们把apt11设置成nil:

这时,也没有任何strong reference指向apt11了,它也会被ARC释放。
这就是当引起reference cycle的两个member允许为nil时,我们使用的解决方案。所以
weak reference用于解决成员允许为nil的reference cycle 。
2. 当有一个member不能为nil时
来看另外一个应用场景。我们把Person改成下面这样:
class Person {
let name: String
var apartment: Apartment?
var property: Apartment? // omit for simplicity...
}
由于不是每个人名下都有房产,因此property允许为nil,我们把它定义为Apartment optional。接下来,把Apartment的代码改成这样:
class Apartment {
let unit: String
weak var tenant: Person?
let owner: Person
init(unit: String, owner: Person) {
self.unit = unit
self.owner = owner
print("Apartment \(unit) is being initialized.")
} // omit for simplicity...
}
由于每个Apartment一定会有房东,不能为nil,因此,我们把它定义为Person。
接下来,当我们再次创建mars和apt11的时候:
var mars: Person? = Person(name: "Mars")
var apt11: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt11
apt11!.tenant = mars
mars = nil
apt11 = nil

在上面这个图里,即使我们把mars和apt11设置为nil:

owner这个strong reference会让mars存活,进而apartment会让apt11存活。这就形成了一个新的reference cycl。
解决办法很简单,我们把owner设置为unowned就ok了!
class Apartment {
let unit: String
weak var tenant: Person?
unowned let owner: Person // Omit for simplicity...
}
和strong reference相比,unowned reference只有一个特别:不会引起对象引用计数的变化。因此,当我们把mars和apt11设置为nil时,对象的关系就会变成这样:

此时,已经没有strong reference引用mars,它会先被ARC释放掉,之后,引用apt11的apartment也不存在了,apt11也会被ARC清除。这就是reference cycle中一方不能为nil时的解决方案。
3.当两个member都不允许为nil的时候
一个国家不可能没有首都,一个城市也不可能没有国家归属,因此,它们都只能是普通的类对象,而不能是一个Optional。接下来,我们来处理它们的初始化问题。首先,我们按照一般的方式处理City:
class City {
let name: String
let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
然后,处理Country
class Country {
let name: String
var capital: City !
init(name: String, capitalName: String) {
self.name = name
self.capital = City(name: capitalName, country: self)
}
}
我们分别定义一个变量:
var cn: Country? = Country(name: "China", capitalName: "Beijing")
var bj: City? = City(name: "Beijing", country: cn!)
cn = nil
bj = nil
经历过之前的多个例子之后,我们很快就可以发现,当cn和bj为nil时,cn.captical和其内建的City仍旧会保持彼此“存活”在内存里,而解决这个问题的办法其实之前我们已经处理过了,把country定义为unowned就可以了。
“unowned reference和implicitly unwrapped optional配合在一起,用于解决引起reference cycle的两个成员都不允许为nil的情况。”