上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
Person
和 Apartment
的例子展示了两个属性的值都允许为 nil
,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
Customer
和 CreditCard
的例子展示了一个属性的值允许为 nil
,而另一个属性的值不允许为 nil
,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 nil
。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
下面的例子定义了两个类,Country
和 City
,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,Country
类拥有一个 capitalCity
属性,而 City
类有一个 country
属性:
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
为了建立两个类的依赖关系,City
的构造器接受一个 Country
实例作为参数,并且将实例保存到 country
属性。
Country
的构造器调用了 City
的构造器。然而,只有 Country
的实例完全初始化后,Country
的构造器才能把 self
传给 City
的构造器。在 两段式构造过程 中有具体描述。
为了满足这种需求,通过在类型结尾处加上感叹号(City!
)的方式,将 Country
的 capitalCity
属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,capitalCity
属性的默认值为 nil
,但是不需要展开它的值就能访问它。在 隐式解包可选值 中有描述。
由于 capitalCity
默认值为 nil
,一旦 Country
的实例在构造器中给 name
属性赋值后,整个初始化过程就完成了。这意味着一旦 name
属性被赋值后,Country
的构造器就能引用并传递隐式的 self
。Country
的构造器在赋值 capitalCity
时,就能将 self
作为参数传递给 City
的构造器。
上述的意义在于你可以通过一条语句同时创建 Country
和 City
的实例,而不产生循环强引用,并且 capitalCity
的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印“Canada's capital city is called Ottawa”
在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。capitalCity
属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
网友评论