自動引用計數(shù)
swift使用自動引用計數(shù)(ARC)機制來跟蹤和管理你的應用程序的內(nèi)存。通常情況下,Swift內(nèi)存管理機制會一直起作用,我們無須自己來考慮內(nèi)存的管理。ARC會在類的實例不再被使用時,自動釋放其占用的內(nèi)存。
note:引用計數(shù)僅僅應用于類的實例。結(jié)構(gòu)體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。
自動引用計數(shù)的工作機制
為了確保使用的實例不會被銷毀,ARC會跟蹤和計算每一個實例正在被多數(shù)屬性,常量和變量所引用。哪怕實例的引用數(shù)為1,ARC都不會銷毀這個實例。
為了使上述成為可能,無論你將實例賦值給屬性、常量或變量,它們都會創(chuàng)建此實例的強引用。之所以稱為“強”引用,是因為它會將實例牢牢的保持住,只要強引用還在,實例就不允許被銷毀的。
類實例之間的循環(huán)強引用
我們可能會寫出一個類實例的強引用數(shù)永遠不能變成0的代碼。如果兩個類實例互相持有對方的強引用,因而每個實例都讓對方一直存在,就是這種情況。這就是所謂的循環(huán)強引用。
我們可以通過定義類之間的關系為弱引用或無主引用,以替代強引用,從而解決循環(huán)強引用的問題。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String) {
self.name = name
}
var a:A?
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b")
a.b = b
b.a = a
}
test()
解決實例之間的循環(huán)強引用
Swift提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環(huán)強引用問題:弱引用(weak reference)和無主引用(unowned reference)。
弱引用和無主引用允許循環(huán)引用中的一個實例引用另外一個實例而不保持強引用。這樣實例能夠相互引用而不產(chǎn)生循環(huán)強引用。
對于生命周期中會變?yōu)閚il的實例使用弱引用。相反地,對于初始化賦值后再也不會被賦值為nil的實例,使用無主引用。
弱引用
弱引用不會對引用的實例保持強引用,因而不會阻止ARC銷毀被引用的實例。這個特性阻止了引用變?yōu)檠h(huán)強引用。聲明屬性或者變量時,在前面加上weak關鍵字聲明這是一個弱引用。
在實例的生命周期中,如果某些時候引用沒有值,哪么弱引用可以避免循環(huán)強引用。如果引用總是有值,則可以使用無主引用。
note: 弱引用必須被聲明為變量,表明其值能在運行時被修改。弱引用不能被聲明為常量。 弱引用可以沒有值,我們必須將每一個弱引用聲明為可選類型。在Swift中,推薦使用可選類型描述可能沒有值的類型。
因為弱引用不會保持所引用的實例,即使引用存在,實例也有可能被銷毀。因此,ARC會在引用的實例被銷毀后自動將其賦值為nil。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String) {
self.name = name
}
weak var a:A?
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b")
a.b = b
b.a = a
}
test()
無主引用
和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用是永遠有值的。因此,無主引用總被定義為非可選類型。我們可以在聲明屬性或者變量時,在前面添加關鍵字unowned表示這是一個無主引用。
由于無主引用是非可選類型,我們不需要在使用它的時候?qū)⑵湔归_。無主引用總是可以被直接訪問。不過ARC無法在實例被銷毀后將無主引用設為nil,因為非可選類型的變量不允許被賦值為nil。
note:如果我們試圖在實例被銷毀后,訪問該實例的無主引用,會觸發(fā)運行時錯誤。使用無主引用,我們必須確保引用始終指向一個未銷毀的實例。
還需要注意的是如果我們試圖訪問實例已經(jīng)被銷毀的無主引用,Swift確保程序會直接奔潰,而不會發(fā)生無法預期的行為。所以我們應當避免這樣的事情發(fā)生。
class A{
let name:String
init(name:String){
self.name = name
}
var b: B?
deinit{
print("a \(name) is dead")
}
}
class B {
let name:String
init(name:String,a:A ) {
self.name = name
self.a = a
}
unowned var a:A
deinit{
print("b \(name) is dead")
}
}
func test(){
let a = A(name: "obj a")
let b = B(name: "obj b",a:a)
}
test()
無主引用以及隱式解析可選屬性
還存在著第三種場景,在這種場景中,兩個屬性都必須有值,并且初始化完后永遠不會為nil。在這種場景中,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性。
這使兩個屬性在初始化完成后被直接訪問(不需要可選展開),同時避免了循環(huán)引用。
class Country {
let name: String
var capitalCity: City!
init(name:String,capitalCityName:String){
self.name = name
self.capitalCity = City(name: capitalCityName, country: self)
}
deinit{
print("country dead")
}
}
class City{
let name:String
unowned let country: Country
init(name:String,country:Country){
self.name = name
self.country = country
}
deinit{
print("city dead")
}
}
func test(){
var country = Country(name: "china", capitalCityName: "beijing")
let city = country.capitalCity
print("\(country.name) and \(country.capitalCity.name)")
print("\(city.country.name)and \(city.country.capitalCity.name) ")
}
test()
閉包引起的循環(huán)強引用
循環(huán)強引用還會發(fā)生在當我們將一個閉包賦值給類實例的某個屬性,并且這個閉包中又使用了這個類的實例。這個閉包中可能訪問了實例的某個屬性(self.someProperty),或者閉包中調(diào)用了實例的某個方法(self.someMethod),這兩種情況都導致閉包捕獲self,從而產(chǎn)生了循環(huán)強引用。
循環(huán)強引用的產(chǎn)生,是因為閉包和類相似,都是引用類型。當我們把一個閉包賦值給某個屬性時,你也把一個引用賦值給這個閉包。實質(zhì)上,這跟之前的問題一樣的---兩個強引用讓彼此一有效。
class HTMLElement{
let name:String
let text:String?
lazy var asHTML: Void ->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{
print("\(name) is dead")
}
}
func test(){
var html = HTMLElement(name: "h1", text: "hello world!")
print(html.asHTML())
}
test()
note:雖然閉包多次使用了self,它只捕獲HTMLElement實例的一個強引用。
解決閉包引起的循環(huán)強引用
在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和實例之間的循環(huán)強引用。捕獲列表定義了閉包體內(nèi)捕獲一個或多個引用類型的規(guī)則。跟解決兩個類實例間的循環(huán)強引用一樣,聲明每個捕獲的引用為弱引用或無主引用,而不是強引用。應當根據(jù)代碼關系來決定使用弱引用還是無主引用。
note:swift有如下要求:只要閉包內(nèi)使用self的成員,就要用self.someproperty或者self.someMethod(),而不是someproperty或someMethod()。這提醒我們可能一不小心就捕獲了self。
定義捕獲列表
捕獲列表中的每一項都由一個對元素組成,一個元素是weak或unowned關鍵字,另外一個元素是類實例的引用(如self)或初始化過的變量(如delegate = self.delegate?。_@些項在放括號中用逗號分開。
class HTMLElement{
let name:String
let text:String?
lazy var asHTML: Void ->String = {
[weak weakSelf = self] in
if let text = weakSelf!.text {
return "<\(weakSelf!.name)> \(text)</\(weakSelf!.name)>"
}else{
return "</\(weakSelf!.name)>"
}
}
init(name:String,text:String? = nil){
self.name = name
self.text = text
}
deinit{
print("\(name) is dead")
}
}
func test(){
var html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
print(html!.asHTML())
html = nil
}
test()
如果閉包有參數(shù)列表或返回類型,把捕獲列表放在它們前面:
lazy var someClosure: (Int,String)->String = {
[unowned self,weak delegate = self.delegate!] (index:Int,StringToProcess:String)-> String in
//closure statement
}
```
如果閉包沒有指明參數(shù)列表或者返回類型,即它們會通過上下文推斷,哪么可以把捕獲列表和關鍵字in放在閉包最開始的地方:
lazy var someClosure: Void ->String = {
[unowned self,weak delegate = self.delegate!] in
//closure statement
}
```
弱引用和無主引用
在閉包和捕獲的實例總是相引用時并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為無主引用。
相反的,在捕獲的引用可能會變?yōu)閚il時,將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型,并且當引用的實例被銷毀后,弱引用的值會被自動設置為nil。這使我們可以在閉包體內(nèi)檢查它們是否存在。
note:如果被捕獲的引用絕對不會變?yōu)閚il,應該用無主引用,而不是弱引用。
class HTMLElement{
let name:String
let text:String?
lazy var asHTML: Void ->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{
print("\(name) is dead")
}
}
func test(){
var html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
print(html!.asHTML())
html = nil
}
test()