最近在看Swift教程中的ARC部分,看到“解决实例间的强引用环”时,有些疑问,故做此记录。
我们知道Swift采用ARC做内存回收,高效的同时也会出现内存泄漏的问题,即实例间强引用。教程里给出了三种场景并给出了示例代码。前两种比较好理解,看的时候,我对第三种有些疑惑。该场景是这样的:两个属性都必须有值,且初始化完成后不能为nil。这种场景下,则要一个类用无主引用属性,另一个类用 隐式展开的可选属性。我的疑惑是为什么要用隐式展开的可选属性,为什么不能用普通类型的常量呢?OK,先看教程中的例子:
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
}
}
也就是,var capitalCity: City!
能不能用 let capitalCity: City
代替?看起来是可以的,挺满足要求的:初始化后不能为nil。但是如果用 let capitalCity: City
会不满足Swift的 两段式构造过程 :
阶段1:
某个指定构造器或便利构造器被调用;
完成新实例内存的分配,但此时内存还没有被初始化;
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
指定构造器将调用父类的构造器,完成父类属性的初始化;
这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段2:
从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。
即不满足阶段二:访问self时,类成员变量必须初始化。想要初始化成员变量必须调用 City
的初始化方法,此时需要访问self,但是此时成员变量 capitalCity
却没有初始化,根据初始化过程规则此时不能访问self,这就造成了矛盾。而使用var capitalCity: City!
不会出现问题,因为其默认值为nil。在调用 City
初始化方式的时候,capitalCity
已经初始化过了,其值彼时为nil。
当然你可以自己试下,改成 let capitalCity: City
后,编译器会报如下错误:
Playground execution failed: error: Constructor.xcplaygroundpage:30:28: error: 'self' used before all stored properties are initialized
self.capitalCity = City(name: capitalName, country: self)
^
Constructor.xcplaygroundpage:27:9: note: 'self.capitalCity' not initialized
var capitalCity: City
^
error: Constructor.xcplaygroundpage:30:61: error: variable 'self.capitalCity' used before being initialized
self.capitalCity = City(name: capitalName, country: self)
^