標籤:ios arc 循環參考 swfit
原創Blog,轉載請註明出處
http://blog.csdn.net/column/details/swfitexperience.html
備忘:本文代碼和圖片主要來自於官方文檔
不熟悉ARC的同學可以看看前一篇關於ARC的簡述,這個是我的Swfit教程專欄
http://blog.csdn.net/column/details/swift-hwc.html
一、幾個用到的關鍵概念
弱引用(weak):不會增加自動引用計數,必須為可選類型變數,因為弱引用在引用計數為0的時候,會自動賦為nil。在swfit中,可以賦值為nil的為可選類型
無主引用(unonwed):不會增加自動引用計數,必須為非可選類型。在ARC銷毀記憶體後,不會被賦為nil,所以在訪問無主引用的時候,要確保其引用正確,不然會引起記憶體崩潰。
隱式解析可選類型:在初始的時候可以為nil,但是第一次賦值以後便會一直有值。文法是在變數後面加上驚嘆號(例如var name:String!)。使用該類型只需要正常調用,不需要像可選類型那樣做判斷。
二、類執行個體之間的循環參考
1、執行個體A可選包含執行個體B的引用,執行個體B可選包含執行個體A的引用-用弱引用來解決
舉例:
下面兩個類,公寓不一定有住戶,住戶也不一定在公寓裡
反面教材:兩個都是強引用會導致循環參考
class Person {let name: Stringinit(name: String) { self.name = name }var apartment: Apartment?deinit { println("\(name) is being deinitialized") }}class Apartment {let number: Intinit(number: Int) { self.number = number }var tenant: Person?deinit { println("Apartment #\(number) is being deinitialized") }}var john: Person?var number73: Apartment?john = Person(name: "John Appleseed")number73 = Apartment(number: 73)john!.apartment = number73number73!.tenant = john然後,這樣就形成了循環參考(此時兩個執行個體引用計數都為2),1.1
然後將兩個強引用斷開後,本應該釋放的記憶體
john = nilnumber73 = nil
這時候記憶體1.2
由於兩個執行個體相互存在強引用(引用計數一直為1),所以這塊記憶體一直沒辦法釋放。
解決方案,採用弱引用,
class Person {let name: Stringinit(name: String) { self.name = name }var apartment: Apartment?deinit { println("\(name) is being deinitialized") }}class Apartment {let number: Intinit(number: Int) { self.number = number }weak var tenant: Person?deinit { println("Apartment #\(number) is being deinitialized") }}var john: Person?var number73: Apartment?john = Person(name: "John Appleseed")number73 = Apartment(number: 73)john!.apartment = number73number73!.tenant = john此時,記憶體配置圖1.3,此時Person執行個體引用計數為1,Apartment執行個體引用計數為2
然後將兩個強引用斷開後,
john = nilnumber73 = nil
記憶體1.4
這時候,Person引用計數為0,Apartment執行個體引用計數為1
由於Person執行個體引用計數為0,Person記憶體被釋放,導致Apartment執行個體引用計數為0,記憶體被釋放
2、執行個體A可選包含執行個體B,執行個體B一定包含執行個體A-用無主引用解決
舉例
使用者可能沒有信用卡,但是信用卡一定會有使用者。由於信用卡一定有使用者,所以不是可選類型,不能用弱引用,swift中提供的無主引用是簡單便捷的解決方案。
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?john = Customer(name: "John Appleseed")john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
這樣記憶體2.1,此時使用者執行個體引用為1,信用卡執行個體引用為1
使用者登出後,
join = nil
那麼使用者執行個體引用計數為0,導致使用者執行個體被釋放,導致信用卡執行個體引用為0,記憶體釋放,2.2
看到這,聰明的同學會問了:由於Customer中的信用卡是可選的,我把它設為弱引用不能解決這個問題嗎?
舉例
class Customer { let name: String weak var card: CreditCard? init(name: String) { self.name = name } deinit { println("\(name) is being deinitialized") }}class CreditCard { let number: Int let customer: Customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { println("Card #\(number) is being deinitialized") }}var john: Customer?john = Customer(name: "John Appleseed")john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
此時的記憶體模型
可以看到問題了吧?由於card只有一個弱引用,也就是引用計數為0,這樣的對象在建立之後就會被釋放掉。所以,沒辦法實現上述功能了。
3、A一定包含B,B一定包含A - 用隱式解析+無主引用解決
舉例:國家一定包含首都,首都也一定在一個國家裡
class Country {let name: Stringlet capitalCity: City!init(name: String, capitalName: String) {self.name = nameself.capitalCity = City(name: capitalName, country: self)}}class City {let name: Stringunowned let country: Countryinit(name: String, country: Country) {self.name = nameself.country = country}}
這裡,Country的建構函式裡,City要調用self,而只有Country的執行個體完全初始化結束後才能調用self。所以,capitialCity設為隱式可選類型,讓他預設為nil,這樣構造過程的第一階段就可以不包括captialCity,就可以把self賦值給Country賦值給capittalCity了。
想詳細看看構造過程的兩個階段,參照我之前寫的構造過程文章,還不懂的話請留言。
這樣設計的意義是:可以通過一條構造與巨還構造國家和首都兩個執行個體,並且可以不用可選解析的方式來訪問首都執行個體。
var country = Country(name: "Canada", capitalName: "Ottawa")println("\(country.name)'s capital city is called \(country.capitalCity.name)")
IOS中解決ARC類執行個體間循環參考(Swfit)