处理对象reference cycle的三种方式
泊学高清学习视频
泊阅开发者文档
我们在上一段视频里了解了reference cycle的成因。这次,我们来了解具体的解决方案。Reference cycle的解决方式,根据class member是否允许为nil,有着不同的处理方式。我们来分别看一下它们:
class member允许为nil时 - weak reference
在我们之前的例子里,Apartment和Person中引起reference cycle的数据成员都允许为nil,对于这种情况,我们可以像下面这样使用weak reference来解决reference cycle:
weak var name: Type
对于一个weak reference来说:
“由于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。”
——特别提示
当有一个member不能为nil时 - unowned
来看另外一个应用场景。我们把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
我们不难发现,添加的owner又引入了一个reference cycle。
在上面这个图里,即使我们把mars和apt11设置为nil:
owner这个strong reference会让mars存活,进而apartment会让apt11存活。这就形成了一个新的reference cycl。
解决这种类型的reference cycle也很简单,我们把用下面的方式:
unowned let name: Type
把owner设置为一个unowned reference就可以了
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时的解决方案。
“unowned reference用于解决成员不允许为nil的reference cycle。”
——特别提示
当两个member都不允许为nil时
这种情况相对复杂一些,我们要让两个类成员互相配合处理这个问题。为了简单说明这种情况,我们重新定义两个类:
class Country { let name: String init(name: String) { self.name = name } } class City { let name: String init(name: String) { self.name = name } }
起初,它们很简单,Country代表国家,City代表城市,各自的init()函数用来构建对象。接下来,我们希望添加下面这样的语义:Country要有一个member表示首都,City要有一个country表示城市的归属。我们先做第一步的修改:
class Country { let name: String var capital: City init(name: String) { self.name = name } } class City { let name: String let country: Country init(name: String) { self.name = name } }
在我们的例子里,一个国家不可能没有首都,一个城市也不可能没有国家归属,因此,它们都只能是普通的类对象,而不能是一个Optional。接下来,我们来处理它们的初始化问题。首先,我们按照一般的方式处理City:
class City { let name: String let country: Country init(name: String,country: Country) { self.name = name self.country = country } }
然后,处理Country的init():
class Country { let name: String var capital: City init(name: String,capitalName: String) { self.name = name // Syntax Error!!! self.capital = City(name: capitalName,country: self) } }
在Country的init()里,我们把正在创建的Country对象传递给了City的init(),这样做,只在语义上是正确的,语法上,Swift认为构建City时,Country还没有完成初始化(self.captical还没有确定的值),因此,它不允许我们在这个时候把self传递给Country。
要想构建City时,让Swift认为Country已经构造完,唯一的做法就是captical有一个默认值nil。至此,对于Capital,我们有了两个看似冲突的需求:
对Country的用户来说,不能让他们知道capital是一个optional;
对Country的设计者来说,它必须像Optional一样有一个默认的nil;
而解决这种冲突唯一的办法,就是把capital定义为一个Implicitly Unwrapped Optional。
class Country { let name: String // Implicitly Unwrapped Optional var capital: City! // default to nil init(name: String,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仍旧会保持彼此“存活”在内存里,而解决这个问题的办法其实之间我们已经处理过了,把captical定义为unowned就可以了。
“unowned reference和implicitly unwrapped optional配合在一起,用于解决引起reference cycle的两个成员都不允许为nil的情况。”
——特别提示
接下来
在这段视频里,我们了解了处理类对象reference cycle的3种不同的方式。在下一段视频里,我们会发现,使用Closure会带来类似的问题。我们也会向大家介绍对应的处理办法