IOS中解決ARC類例項間迴圈引用(Swfit)
阿新 • • 發佈:2019-02-16
原創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的引用-用弱引用來解決
舉例:
下面兩個類,公寓不一定有住戶,住戶也不一定在公寓裡
反面教材:兩個都是強引用會導致迴圈引用
然後將兩個強引用斷開後,本應該釋放的記憶體
由於兩個例項相互存在強引用(引用計數一直為1),所以這塊記憶體一直沒辦法釋放。
解決方案,採用弱引用,
然後將兩個強引用斷開後,
這時候,Person引用計數為0,Apartment例項引用計數為1
由於Person例項引用計數為0,Person記憶體被釋放,導致Apartment例項引用計數為0,記憶體被釋放
2、例項A可選包含例項B,例項B一定包含例項A-用無主引用解決
舉例
使用者可能沒有信用卡,但是信用卡一定會有使用者。由於信用卡一定有使用者,所以不是可選型別,不能用弱引用,swift中提供的無主引用是簡單便捷的解決方案。
這樣記憶體如圖2.1,此時使用者例項引用為1,信用卡例項引用為1
使用者登出後,
join = nil
那麼使用者例項引用計數為0,導致使用者例項被釋放,導致信用卡例項引用為0,記憶體釋放,如圖2.2
看到這,聰明的同學會問了:由於Customer中的信用卡是可選的,我把它設為弱引用不能解決這個問題嗎?
舉例
此時的記憶體模型
可以看到問題了吧?由於card只有一個弱引用,也就是引用計數為0,這樣的物件在建立之後就會被釋放掉。所以,沒辦法實現上述功能了。
3、A一定包含B,B一定包含A - 用隱式解析+無主引用解決
舉例:國家一定包含首都,首都也一定在一個國家裡
這裡,Country的建構函式裡,City要呼叫self,而只有Country的例項完全初始化結束後才能呼叫self。所以,capitialCity設為隱式可選型別,讓他預設為nil,這樣構造過程的第一階段就可以不包括captialCity,就可以把self賦值給Country賦值給capittalCity了。
想詳細看看構造過程的兩個階段,參照我之前寫的構造過程文章,還不懂的話請留言。
這樣設計的意義是:可以通過一條構造與巨還構造國家和首都兩個例項,並且可以不用可選解析的方式來訪問首都例項。
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,所以在訪問無主引用的時候,要確保其引用正確,不然會引起記憶體崩潰。
隱式解析可選型別
二、類例項之間的迴圈引用
1、例項A可選包含例項B的引用,例項B可選包含例項A的引用-用弱引用來解決
舉例:
下面兩個類,公寓不一定有住戶,住戶也不一定在公寓裡
反面教材:兩個都是強引用會導致迴圈引用
然後,這樣就形成了迴圈引用(此時兩個例項引用計數都為2),如圖1.1class 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") } } var john: Person? var number73: Apartment? john = Person(name: "John Appleseed") number73 = Apartment(number: 73) john!.apartment = number73 number73!.tenant = john
然後將兩個強引用斷開後,本應該釋放的記憶體
john = nil
number73 = nil
這時候記憶體如圖1.2由於兩個例項相互存在強引用(引用計數一直為1),所以這塊記憶體一直沒辦法釋放。
解決方案,採用弱引用,
此時,記憶體圖如圖1.3,此時Person例項引用計數為1,Apartment例項引用計數為2class 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 } 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 = number73 number73!.tenant = john
然後將兩個強引用斷開後,
john = nil
number73 = 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: 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
}
}
這裡,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)")