首先一張圖解釋一下循環引用
Swift問題解決方案
爲了解決這個問題,swift提供了三種基本的解決方案,如下:
弱引用(Weak Reference)
如果被指向的實例有可能爲nil,則使用弱引用
無主引用(Unowned Reference)
如果被指向的實例不爲nil,則使用無主引用
捕獲列表(Capture List)
如果在類屬性使用閉包時,且閉包體內引用當前實例self而產生強引用環時,則使用捕獲列表
下面對這三種方法和使用的情況分別做解釋說明
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//強引用例子
strongReferenceMethod()
//解決方法一,弱引用例子,被指向的實例有可能爲nil的時候
weakReferenceMethod()
//解決方法二,無主引用,被指向的實例不能爲nil的時候
unownedReferenceMethod()
//解決方法三,捕獲列表.如果在類屬性使用閉包時,且閉包內引用當前實例self而產生強引用環時,則使用捕獲列表
captureListMethod()
}
}
func strongReferenceMethod() {
var man:Male? = Male(name: "李雷")
var woman:Female? = Female(name: "韓梅梅")
man!.girlFriend = woman
woman!.boyFriend = man
debugPrint("韓妹妹和李雷完成了人生使命,我們告訴arc這兩個實例不再使用了。")
debugPrint("woman 實例釋放前,man.girlFriend 的值:\(man!.girlFriend)") //"woman 實例釋放前,man.girlFriend 的值:Optional(StrongReferenceCycle.Female)"
woman = nil
debugPrint("woman 實例釋放後,man.girlFriend 的值:\(man!.girlFriend)")
man = nil //"woman 實例釋放後,man.girlFriend 的值:Optional(StrongReferenceCycle.Female)"
//可以看到並沒有調用對應的dealloc銷燬方法
}
func weakReferenceMethod() {
var man:Male2? = Male2(name: "李雷2")
var woman:Female2? = Female2(name: "韓梅梅2")
man!.girlFriend = woman
woman!.boyFriend = man
debugPrint("韓妹妹2和李雷2完成了人生使命,我們告訴arc這兩個實例不再使用了。")
debugPrint("woman 實例釋放前,man.girlFriend 的值:\(man!.girlFriend)")//"woman 實例釋放前,man.girlFriend 的值:Optional(StrongReferenceCycle.Female2)"
debugPrint("man 實例釋放前,woman.boyFriend 的值:\(woman!.boyFriend)")//"man 實例釋放前,woman.boyFriend 的值:Optional(StrongReferenceCycle.Male2)"
woman = nil //"女人2實例被銷燬"
debugPrint("woman 實例釋放後,man.girlFriend 的值:\(man!.girlFriend)")//"woman 實例釋放後,man.girlFriend 的值:nil"
man = nil //"男人2實例被銷燬"
debugPrint("man 實例釋放後,woman.boyFriend 的值:\(woman?.boyFriend )")//"man 實例釋放後,woman.boyFriend 的值:nil"這裏需要使用woman?來進行解包,否則因爲woman已經是nil了,如果強制解包就會報錯
//這裏可以看到swift會主動保護弱引用的值。如man實例中的弱引用girlfriend屬性,他指向的是woman屬性,所以當woman實例釋放,則girlfriend值會被swift重置爲nil
}
func unownedReferenceMethod() {
//無主引用總是假定其引用對象一直存在(有值),因而無主引用不能修飾可選類型。使用場景1.其中一個類實例中國的屬性爲可選類型,即其值可以爲nil,而另一個實例中的屬性爲非可選類型,即不能爲nil。場景2.兩個類實例中的屬性,一旦初始化後,都不能爲nil
var stark: Adult?
stark = Adult(name: "柰徳.斯塔克")
stark!.child = Child(name: "羅伯.斯塔克", guardian: stark!)
stark = nil
}
func captureListMethod() {
var weatherReport: WeatherReport? = WeatherReport(location: "成都", weather: "多雲")
debugPrint("\(weatherReport!.reports())") //"成都的天氣預報是:多雲"
weatherReport = nil //但是這個執行後並沒有調用dealloc方法釋放
var weatherReport2: WeatherReport2? = WeatherReport2(location: "成都", weather: "多雲")
debugPrint("\(weatherReport2!.reports())") //"成都的天氣預報是:多雲"
weatherReport2 = nil //"成都的天氣預報實例被銷燬!" 這裏可以看到正確執行了dealloc方法
}
class Male {
let name: String
var girlFriend: Female? //這裏互相引用
init(name: String) {
self.name = name
}
deinit {
debugPrint("男人實例被銷燬")
}
}
class Female {
let name: String
var boyFriend: Male? //這裏互相引用
init(name: String) {
self.name = name
}
deinit {
debugPrint("女人實例被銷燬")
}
}
class Male2 {
let name: String
weak var girlFriend: Female2? //弱引用必須是變量,因爲有可能在運行過程中值被修改了
init(name: String) {
self.name = name
}
deinit {
debugPrint("男人2實例被銷燬")
}
}
class Female2 {
let name: String
weak var boyFriend: Male2? //弱引用必須是變量,因爲有可能在運行過程中值被修改了
init(name: String) {
self.name = name
}
deinit {
debugPrint("女人2實例被銷燬")
}
}
class Adult {
let name: String
init(name: String) {
self.name = name
}
var child: Child?
deinit {
debugPrint("adult\(name)被銷燬!")
}
}
class Child {
let name: String
unowned var guardian:Adult //不能爲nil的實例標註爲unowned類型
init(name: String, guardian: Adult) {
self.name = name
self.guardian = guardian
}
deinit {
debugPrint("child\(name)被銷燬!")
}
}
class WeatherReport {
let location: String
let weather: String
var temperature: Int?
//因爲該計算屬性使用到閉包且閉包中使用了self,所以必須是惰性屬性.因爲此時self和其他使用到的屬性有可能還沒有初始化完成就執行了reports屬性內部的操作
lazy var reports: ()->String = {
if self.temperature != nil {
return "\(self.location)的天氣預報是: \(self.weather), 氣溫是\(self.temperature)"
} else {
return "\(self.location)的天氣預報是:\(self.weather)"
}
}
init(location: String, weather: String) {
self.location = location
self.weather = weather
}
deinit {
debugPrint("\(location)的天氣預報實例被銷燬!")
}
}
class WeatherReport2 {
let location: String
let weather: String
var temperature: Int?
//因爲該計算屬性使用到閉包且閉包中使用了self,所以必須是惰性屬性.因爲此時self和其他使用到的屬性有可能還沒有初始化完成就執行了reports屬性內部的操作
lazy var reports: ()->String = {
[weak self] in //[weak self] in 和 [unowned self] in作用一樣,區別見前面的weak和unowned的解釋
if self!.temperature != nil {
return "\(self!.location)的天氣預報是: \(self!.weather), 氣溫是\(self!.temperature)"
} else {
return "\(self!.location)的天氣預報是:\(self!.weather)"
}
}
init(location: String, weather: String) {
self.location = location
self.weather = weather
}
deinit {
debugPrint("\(location)的天氣預報實例被銷燬!")
}
}