縱然Swift使用ARC(Automatic Reference Counting)為我們打理內存,這并不代表它面對任何情況都足夠聰明趾断。尤其是當對象之間存在相互引用的時候匆浙,更是容易由于reference cycle導致內存無法釋放刃泌。當然,這并非我們本意勺远,只是有時這樣的問題發(fā)生的不甚明顯橙喘。Swift為我們提供了一系列語言機制來處理reference cycle,而我們也應該時刻保持警醒胶逢,避免內存泄漏厅瞎。
import UIKit
class Person {
let name :String
var apartment: Apartment?
var property: 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?
unowned let owner: Person
//和strong reference相比饰潜,unowned reference只有一個特別:不會引起對象引用計數(shù)的變化。unowned reference用于解決成員不允許為nil的reference cycle磁奖。
init(unit: String, owner: Person) {
self.unit = unit
self.owner = owner
print("Apartment \(unit) is being initialized.")
}
deinit {
print("Apartment \(unit) is being deinitialized.")
}
}
//: Strong reference
var ref1 :Person?
var ref2 :Person?
ref1 = Person(name: "Mars")
// count = 2
ref2 = ref1
// count = 1
ref1 = nil
// count = 0
// Mars is being deinitialized.
ref2 = nil // is being deinitialized 銷毀時調用
var mars :Person? = Person(name: "Mars")
var apt11: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt11
// mars.count = 2
apt11!.tenant = mars
這時囊拜,盡管我們把mars和apt11設置為nil,Person和Apartmetn的deinit也不會被調用了比搭。/因為它們的兩個member(apartment和tenant)是一個strong reference冠跷,指向了彼此,讓對象仍舊“存活”在內存里身诺。
//但是蜜托,mars和apt11已經(jīng)被設置成nil,我們也已經(jīng)無能為力了霉赡。這就是類對象之間的reference cycle橄务。
mars = nil
apt11 = nil
處理對象reference cycle的三種方式
1: weak var tenant: Person? “weak reference用于解決成員允許為nil的reference cycle⊙鳎”
2: unowned let “unowned reference用于解決成員不允許為nil的reference cycle蜂挪。”
3: unowned reference和implicitly unwrapped optional配合在一起嗓化,用于解決引起reference cycle的兩個成員都不允許為nil的情況棠涮。
class Country {
let name: String
var capital: City! // default to nil
init(name: String, capitalName: String) {
self.name = name
// Syntax Error!!!
self.capital = City(name: capitalName, country: self)
}
deinit {
print("Country \(name) is being deinitialized.")
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit {
print("City \(name) is being deinitialized.")
}
}
var cn: Country? = Country(name: "China", capitalName: "Beijing")
var bj: City? = City(name: "Beijing", country: cn!)
cn = nil
bj = nil
要想構建City時,讓Swift認為Country已經(jīng)構造完刺覆,唯一的做法就是captical有一個默認值nil严肪。至此,對于Capital谦屑,我們有了兩個看似沖突的需求:
對Country的用戶來說驳糯,不能讓他們知道capital是一個optional;
對Country的設計者來說氢橙,它必須像Optional一樣有一個默認的nil酝枢;
而解決這種沖突唯一的辦法,就是把capital定義為一個Implicitly Unwrapped Optional (隱式解析可選)悍手。
處理closure和類對象之間的reference cycle
class HTMLElment {
let name: String
let text: String?
//“l(fā)azy可以確保一個成員只在類對象被完整初始化過之后隧枫,才能使用∥焦叮”
lazy var asHTML: (Void) -> String = {
// text
// Capture list 由于HTMLElement沒有了strong reference,因此它會被ARC釋放掉协怒,進而asHTML引用的closure也會變成“孤魂野鬼”涝焙,ARC當然也不會放過它。因此孕暇,closure和類對象間的循環(huán)引用問題就解決了仑撞。
//在這里赤兴,關于closure capture list,我們要多說兩點:
//如果closure帶有完整的類型描述隧哮,capture list必須寫在參數(shù)列表前面桶良;
//如果我們要在capture list里添加多個成員,用逗號把它們分隔開沮翔;
[unowned self /*, other capture member*/] () -> String in
if let text = self.text {
return "<\(self.name)>\(self.text)</\(self.name)>"
}
else {
return "<\(self.name)>"
}
}
//h1是我們定義的strong reference陨帆。Closure作為一個引用類型,它有自己的對象采蚀,因此asHTML也是一個strong reference疲牵。
//由于asHTML“捕獲”了HTMLElement的self,因此HTMLElement的引用計數(shù)是2榆鼠。
//當h1為nil時纲爸,asHTML對closure的引用和closure對self的“捕獲”就形成了一個reference cycle。
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(self.name) is being deinitialized")
}
}
var h1: HTMLElment? = HTMLElment(name: "h1", text: "Title")
h1?.asHTML
//“當一個類中存在訪問數(shù)據(jù)成員的closure member時妆够,務必要謹慎處理它有可能帶來的reference cycle問題识啦。”
h1 = nil