Swift使用自動引用計數(ARC)來跟蹤并管理應用使用的內存。大部分情況下,這意味著在Swift語言中,內存管理"仍然工作",不需要自己去考慮內存管理的事情。當實例不再被使用時,ARC會自動釋放這些類的實例所占用的內存。然而,在少數情況下,為了自動的管理內存空間,ARC需要了解關于你的代碼片段之間關系的更多信息。本章描述了這些情況,并向大家展示如何打開ARC來管理應用的所有內存空間。
class Person { let name: String init(name: String) { self.name = name println("\(name) is being initialized") } deinit { println("\(name) is being deinitialized") }
}
?
var reference1: Person?
var reference2: Person?
var reference3: Person?
?現在我們創建一個新的Person實例,并且將它賦值給上述三個變量中的一個:
reference1 = Person(name: "John Appleseed") // prints "Jonh Appleseed is being initialized"
reference2 = reference1
reference3 = reference2
?
?
reference1 = nil
reference2 = nil
直到第三個也是最后一個強引用被破壞,ARC才會銷毀Person的實例,這時,有一點非常明確,你無法繼續使用Person實例:
referenece3 = nil // 打印 “John Appleseed is being deinitialized”
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } } class Apartment { let unit: Int init(unit: Int) { self.unit= unit } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } }
?
var john: Person?
var unit4A: Apartment?
?現在,你可以創建特定的Person實例以及Apartment實例,并賦值給john和number73:
jhon = Person(name: "John Appleseed")
unit4A = Apartments(number: 4A)
?下面的圖表明了在創建以及賦值這兩個實例后強引用的關系。john擁有一個Person實例的強引用,unit4A擁有一個Apartment實例的強引用:
?現在你可以將兩個實例關聯起來,一個人擁有一所公寓,一個公寓也擁有一個房客。注意:用感嘆號(!)來展開并訪問可選類型的變量,只有這樣這些變量才能被賦值:
john!.apartment = unit4A
unit4A!.tenant = john
?
?實例關聯起來后,強引用關系如下圖所示
?
?關聯這倆實例生成了一個強循環引用,Person實例和Apartment實例各持有一個對方的強引用。因此,即使你破壞john和number73所持有的強引用,引用計數也不會變為0,因此ARC不會銷毀這兩個實例
?
john = nil
unit4A = nil
當上面兩個變量賦值為nil時,沒有調用任何一個析構方法。強引用阻止了Person和Apartment實例的銷毀,進一步導致內存泄漏。
?
避免強引用循環
Swift提供兩種方法避免強引用循環:弱引用和非持有引用。?
? 對于生命周期中引用會變為nil的實例,使用弱引用;對于初始化時賦值之后引用再也不會賦值為nil的實例,使用非持有引用。
?
弱引用
弱引用不會增加實例的引用計數,因此不會阻止ARC銷毀被引用的實例,聲明屬性或者變量的時候,關鍵字weak表明引用為弱引用。弱引用只能聲明為變量類型,因為運行時它的值可能改變。弱引用絕對不能聲明為常量
? 因為弱引用可以沒有值,所以聲明弱引用的時候必須是可選類型的。在Swift語言中,推薦用可選類型來作為可能沒有值的引用的類型。
?下面的例子和之前的Person和Apartment例子相似,除了一個重要的區別。這一次,我們聲明Apartment的tenant屬性為弱引用:
class Person {let name: Stringinit(name: String) { self.name = name }var apartment: Apartment?deinit { print("\(name) is being deinitialized") }
}class Apartment {let unit: Stringinit(unit: String) { self.unit = unit }weak var tenant: Person?deinit { print("Apartment \(unit) is being deinitialized") }
}
?
?然后創建兩個變量(john和unit4A)的強引用,并關聯這兩個實例:
var john: Person?
var unit4A: Apartment?john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")john!.apartment = unit4A
unit4A!.tenant = john
?
?下面是引用的關系圖:
?
Person的實例仍然是Apartment實例的強引用,但是Apartment實例則是Person實例的弱引用。這意味著當破壞john變量所持有的強引用后,不再存在任何Person實例的強引用:?
?
?既然不存在Person實例的強引用,那么該實例就會被銷毀:
?
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { println("\(name) is being deinitialized") } class CreditCard { let number: Int unowned let customer: Customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { println("Card #\(number) is being deinitialized") }
?
var john: Customer?
?現在創建一個Customer實例,然后用它來初始化CreditCard實例,并把剛創建出來的CreditCard實例賦值給Customer的card屬性:
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer:john!)
?此時的引用關系如下圖所示
因為john對CreditCard實例是非持有引用,當破壞john變量持有的強引用時,就沒有Customer實例的強引用了
此時Customer實例被銷毀。然后,CreditCard實例的強引用也不復存在,因此CreditCard實例也被銷毀
john = nil
// 打印"John Appleseed is being deinitialized"
// 打印"Card #1234567890123456 is being deinitialized"
??
非持有引用以及隱式展開的可選屬性
?下面的例子定義了兩個類,Country和City,都有一個屬性用來保存另外的類的實例。在這個模型里,每個國家都有首都,每個城市都隸屬于一個國家。所以,類Country有一個capitalCity屬性,類City有一個country屬性:
class Country { let name: String let 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 country = Country(name: "Canada", capitalName: "Ottawa") println("\(country.name)'s captial city is called \(country.capitalCity.name)") // 打印"Canada's capital city is called Ottawa"
?
?在上面的例子中,使用隱式展開的可選值滿足了兩個類的初始化函數的要求。初始化完成后,capitalCity屬性就可以做為非可選值類型使用,卻不會產生強引用環。
?
閉包的強引用循環
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { println("\(name) is being deinitialized") } }
?
?閉包使用了self(引用了self.name和self.text),因此閉包占有了self,這意味著閉包又反過來持有了HTMLElement實例的強引用。這樣就產生了強引用環
?
避免閉包產生的強引用循環
?
? 定義捕獲列表
?
lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in // closure body goes here }
?
?如果閉包沒有指定參數列表或者返回類型(可以通過上下文推斷),那么占有列表放在閉包開始的地方,跟著是關鍵字in:
lazy var someClosure: () -> String = { [unowned self] in // closure body goes here }
?前面提到的HTMLElement例子中,非持有引用是正確的解決強引用的方法。這樣編碼HTMLElement類來避免強引用環:
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { [unowned self] in if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { println("\(name) is being deinitialized") } }
?
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") println(paragraph!.asTHML()) // 打印"<p>hello, world</p>"
?引用關系如下圖
paragraph = nil // 打印"p is being deinitialized"
?