1. 程式人生 > >IOS中解決ARC類例項間迴圈引用(Swfit)

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: 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
然後,這樣就形成了迴圈引用(此時兩個例項引用計數都為2),如圖1.1


然後將兩個強引用斷開後,本應該釋放的記憶體
john = nil
number73 = nil
這時候記憶體如圖1.2


由於兩個例項相互存在強引用(引用計數一直為1),所以這塊記憶體一直沒辦法釋放。
解決方案,採用弱引用,
class 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
此時,記憶體圖如圖1.3,此時Person例項引用計數為1,Apartment例項引用計數為2


然後將兩個強引用斷開後,
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)")