Swift類實例之間的循環強引用
類實例之間的循環強引用
在上面的例子中,ARC 會跟蹤你所新創建的Person
實例的引用數量,並且會在Person
實例不再被需要時銷燬它。
然而,我們可能會寫出這樣的代碼,一個類永遠不會有0個強引用。這種情況發生在兩個類實例互相保持對方的強引用,並讓對方不被銷燬。這就是所謂的循環強引用。
你可以通過定義類之間的關係爲弱引用或者無主引用,以此替代強引用,從而解決循環強引用的問題。具體的過程在解決類實例之間的循環強引用中有描述。不管怎樣,在你學習怎樣解決循環強引用之前,很有必要了解一下它是怎樣產生的。
下面展示了一個不經意產生循環強引用的例子。例子定義了兩個類:Person
和Apartment
,用來建模公寓和它其中的居民:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { println("\(name) is being deinitialized") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
var tenant: Person?
deinit { println("Apartment #\(number) is being deinitialized") }
}
每一個Person
實例有一個類型爲String
,名字爲name
的屬性,並有一個可選的初始化爲nil
的apartment
屬性。apartment
屬性是可選的,因爲一個人並不總是擁有公寓。
類似的,每個Apartment
實例有一個叫number
,類型爲Int
的屬性,並有一個可選的初始化爲nil
的tenant
屬性。tenant
屬性是可選的,因爲一棟公寓並不總是有居民。
這兩個類都定義了析構函數,用以在類實例被析構的時候輸出信息。這讓你能夠知曉Person
和Apartment
的實例是否像預期的那樣被銷燬。
接下來的代碼片段定義了兩個可選類型的變量john
和number73
,並分別被設定爲下面的Apartment
和Person
的實例。這兩個變量都被初始化爲nil
,併爲可選的:
var john: Person?
var number73: Apartment?
現在你可以創建特定的Person
和Apartment
實例並將類實例賦值給john
和number73
變量:
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)
在兩個實例被創建和賦值後,下圖表現了強引用的關係。變量john
現在有一個指向Person
實例的強引用,而變量number73
有一個指向Apartment
實例的強引用:
現在你能夠將這兩個實例關聯在一起,這樣人就能有公寓住了,而公寓也有了房客。注意感嘆號是用來展開和訪問可選變量john
和number73
中的實例,這樣實例的屬性才能被賦值:
john!.apartment = number73
number73!.tenant = john
在將兩個實例聯繫在一起之後,強引用的關係如圖所示:
不幸的是,將這兩個實例關聯在一起之後,一個循環強引用被創建了。Person
實例現在有了一個指向Apartment
實例的強引用,而Apartment
實例也有了一個指向Person
實例的強引用。因此,當你斷開john
和number73
變量所持有的強引用時,引用計數並不會降爲 0,實例也不會被 ARC 銷燬:
john = nil
number73 = nil
注意,當你把這兩個變量設爲nil
時,沒有任何一個析構函數被調用。強引用循環阻止了Person
和Apartment
類實例的銷燬,並在你的應用程序中造成了內存泄漏。
在你將john
和number73
賦值爲nil
後,強引用關係如下圖:
Person
和Apartment
實例之間的強引用關係保留了下來並且不會被斷開。