Swift使用自動引用計數(ARC)來跟蹤並管理應用使用的內存。大部分狀況下,這意味着在Swift語言中,內存管理"仍然工做",不須要本身去考慮內存管理的事情。當實例再也不被使用時,ARC會自動釋放這些類的實例所佔用的內存。然而,在少數狀況下,爲了自動的管理內存空間,ARC須要瞭解關於你的代碼片斷之間關係的更多信息。本章描述了這些狀況,並向你們展現如何打開ARC來管理應用的全部內存空間。api
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實例:app
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實例的強引用:編碼
如今你能夠將兩個實例關聯起來,一我的擁有一所公寓,一個公寓也擁有一個房客。注意:用感嘆號(!)來展開並訪問可選類型的變量,只有這樣這些變量才能被賦值:spa
john!.apartment = unit4A unit4A!.tenant = john
實例關聯起來後,強引用關係以下圖所示3d
關聯這倆實例生成了一個強循環引用,Person實例和Apartment實例各持有一個對方的強引用。所以,即便你破壞john和number73所持有的強引用,引用計數也不會變爲0,所以ARC不會銷燬這兩個實例code
john = nil
unit4A = nil
當上面兩個變量賦值爲nil時,沒有調用任何一個析構方法。強引用阻止了Person和Apartment實例的銷燬,進一步致使內存泄漏。對象
避免強引用循環blog
Swift提供兩種方法避免強引用循環:弱引用和非持有引用。
對於生命週期中引用會變爲nil的實例,使用弱引用;對於初始化時賦值以後引用不再會賦值爲nil的實例,使用非持有引用。
弱引用
弱引用不會增長實例的引用計數,所以不會阻止ARC銷燬被引用的實例,聲明屬性或者變量的時候,關鍵字weak代表引用爲弱引用。弱引用只能聲明爲變量類型,由於運行時它的值可能改變。弱引用絕對不能聲明爲常量
由於弱引用能夠沒有值,因此聲明弱引用的時候必須是可選類型的。在Swift語言中,推薦用可選類型來做爲可能沒有值的引用的類型。
下面的例子和以前的Person和Apartment例子類似,除了一個重要的區別。這一次,咱們聲明Apartment的tenant屬性爲弱引用:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(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>"
引用關係以下圖
這一次,閉包以無主引用的形式佔有self,並不會持有HTMLElement實例的強引用。若是賦值paragraph爲nil,HTMLElement實例將會被銷燬,並能看到它的deinitializer打印的消息。paragraph = nil // 打印"p is being deinitialized"