ARC:"Automatic Reference Counting",自動引用計數。Swift語言延續了OC的作法,也是利用ARC機制進行內存管理,和OC的ARC同樣,當一些類的實例不在須要的時候,ARC會釋放它們的內存。可是,在少數狀況下,ARC須要知道你的代碼之間的關係才能更好的爲你管理內存,和OC同樣,Swift中的ARC也存在循環引用致使內存泄露的狀況。html
每當咱們建立一個類的新的實例的時候,ARC會從堆中分配一塊內存用來存儲有關該實例的信息。這塊內存將持有這個實例的類型信息以及和它關聯的屬性的值。另外,當這個實例再也不被須要的時候,ARC將回收這個實例所佔有的內存而且將這部份內存給其餘須要的實例用。這樣就能保證再也不被須要的實例不佔用多餘的內存。 可是,若是ARC釋放了正在使用的實例,那麼該實例的屬性將不能被訪問,方法將不能被調用,若是你訪問它的屬性或者調用它的方法時,應用會崩潰,由於你訪問了一個野指針。 爲了解決上述問題,ARC會跟蹤每一個類的實例正在被多少個屬性、常量或者變量引用,每當你將類實例賦值給屬性,常量或者變量的時候它就會被"強"引用一次,當它的引用計數爲0時,代表它再也不被須要,ARC就會銷燬它。 下面舉個例子介紹ARC是如何工做的bash
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
複製代碼
上述代碼建立了一個名爲Person
的類,該類聲明瞭一個非可選的類型的name
常量,一個給name
賦值的初始化方法,而且打印了一句話,用來標註初始化成功,同時聲明瞭一個析構函數,打印了一句標誌此實例被銷燬的信息。閉包
var reference1: Person?
var reference2: Person?
var reference3: Person?
複製代碼
上述代碼聲明瞭三個Person?
類型的變量,這三個變量爲可選類型,因此被自動初始化爲nil
,此時三個實例都沒有指向任何一個Person
類的實例。app
reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"
複製代碼
如今建立一個Person
類的實例,而且賦值給reference1
,此時控制檯會打印"John Appleseed is being initialized"
。函數
reference2 = reference1
reference3 = reference1
複製代碼
而後將該實例賦值給reference2
和reference3
。如今該實例被三個"強"類型的指針引用。ui
reference1 = nil
reference2 = nil
複製代碼
如上所示,當咱們將其中兩個引用賦值給nil
的時候,這兩個"強"引用被打破,可是這個Person
的實例並無被釋放(釋放信息未打印),由於還存在一個對這個實例的強引用。spa
reference3 = nil
// Prints "John Appleseed is being deinitialized"
複製代碼
當咱們將第三個"強"引用打破的時候(賦值爲nil
),能夠看到控制檯打印的"John Appleseed is being deinitialized"
析構信息。3d
上述的例子中,ARC能夠很好的獲取一個實例的引用計數,而且當它的引用計數爲0的時候釋放它。可是在實際的開發過程當中,會存在一些特殊狀況,使ARC沒辦法獲得引用計數爲0這個關鍵點,就會形成這個實例的內存一直不被釋放,兩個類的實例相互"強"引用就會形成這種狀況,就是"循環引用"。 蘋果官方提供了兩種方法來解決兩個實例之間的循環引用,unowned
引用和weak
引用。指針
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
複製代碼
這個例子,定義了一個Person
類和一個Apartment
類。每個Person
的實例都有一個name
的屬性和一個apartment
的可選屬性,初始化爲nil
,由於並非每個人都擁有一個公寓,因此是可選屬性。一樣的,每個Apartment
實例都有一個unit
屬性和一個tenant
的可選屬性,初始化爲nil
,同理,不是每個公寓都有人租。同時,兩個類都定義了deinit
方法,而且打印一段信息,用來讓咱們清楚這個實例什麼時候被銷燬。code
var john: Person?
var unit4A: Apartment?
複製代碼
分別定義一個Person
類型和Apartment
的變量,定義爲optional
(可選類型),初始化爲nil
。
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
複製代碼
而後分別建立一個Person
類的實例和Apartment
類的實例,而且分別賦值給上面的定義的變量。
john
將擁有一座公寓
unit4A
,公寓
unit4A
將被
john
承租。
john!.apartment = unit4A
unit4A!.tenant = john
複製代碼
由於能夠肯定兩個變量都被賦值爲相應類型的實例,因此此處用!
對可選屬性強解包。 此時,兩個變量和實例以及兩個實例之間的"強"引用關係以下圖。
john = nil
unit4A = nil
複製代碼
當咱們將兩個變量設置爲nil
,切斷他們與實例之間的"強"引用關係,此時兩個實例之間的"強"引用關係爲:
Swift提供了兩種辦法解決類實例之間的循環引用。weak
引用和unowned
引用。這兩種方法均可以使一個實例引用另外一個實例的時候,不用保持"強"引用。weak
通常應用於其中一個實例具備更短的生命週期,或者能夠隨時設置爲nil
的狀況下;unowned
用於兩個實例具備差很少長的生命週期,或者說兩個實例都不能被設置爲nil
。
weak
引用對所引用的實例不會保持"強"引用的關係。假如一個實例同時被若干個"強引用"和一個weak
引用引用時,當全部其餘的"強"引用都被打破時該實例就會被ARC釋放,而且ARC會自動將這個weak
引用置爲nil
。所以,weak
引用通常被聲明爲var
,由於它會被ARC設置爲nil
。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
複製代碼
如今,咱們將Apartment
類中的tenant
變量聲明爲weak
引用(在var
關鍵字前加weak
關鍵字),代表某公寓的承租人並不必定一直都是同一我的。
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
複製代碼
而後和上文同樣,將兩個變量和實例關聯。此時,它們之間的引用關係以下圖。
Person
實例仍然"強"引用
Apartment
實例,可是
Apartment
實例'weak'引用
Person
實例。
john
和
unit4A
兩個變量仍然"強"引用兩個實例。當咱們把
john
變量對
Person
實例的"強"引用打破的時候,即將
john
設置爲
nil
,就沒有其餘的"強"引用引用
Person
實例,此時,
Person
實例被ARC釋放,同時
Apartment
實例的
tenant
變量被設置爲
nil
。
john = nil
// Prints "John Appleseed is being deinitialized"
複製代碼
而後將變量
unit4A
設爲
nil
,能夠看到
Apartment
實例也被銷燬。
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
複製代碼
和weak
引用同樣,unowned
引用也不會保持它和它所引用實例之間的"強"引用關係,而是保持一種非擁有(或未知)的關係,使用的時候也是用unowned
關鍵字修飾聲明的變量。不一樣的是,兩個互相引用的對象具備差很少長的生命週期,而不是其中一個能夠提早被釋放(weak
),有點同甘共苦的意思。 Swift要求unowned
修飾的變量必須一直指向一個實例,而不是有些時候爲nil
,所以,ARC也不會將這個變量設置爲nil
,因此咱們通常將這個引用聲明爲非可選類型。PS:請確保你聲明的變量一直指向一個實例,若是這個實例被釋放了,而unowned
變量還在引用它的話,你會獲得一個運行時錯誤,由於,這個變量是非可選類型的。
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
複製代碼
上面這個例子定義了兩個類:Customer
和CreditCard
,每一個顧客均可能會有一張信用卡(可選類型),每一個信用卡都必定會有一個持有他們的顧客(非可選類型,卡片爲顧客定製)。所以,Customer
類有一個CreditCard?
類型的屬性,CreditCard
類也有一個Customer
類型的屬性,而且被聲明爲unowned
,以此來打破循環引用。每張信用卡初始化的時候都須要一名持有它的顧客,由於信用卡自己就是爲顧客定製的。
var john: Customer?
複製代碼
而後聲明一個Customer?
類型的變量john
,初始化爲nil
。接着建立一個Customer
的實例,而且將它賦值給john
(讓john
引用它、指向它都是一個意思)。
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
複製代碼
(第一句代碼賦值以後,咱們知道john
確定不是nil
,因此用!
解包不會有問題) 而後,兩個實例之間的引用關係爲:
Customer
實例"強"引用
CreditCard
實例,
CreditCard
實例'unowned'引用
Customer
實例,接着,咱們將
john
對
Customer
實例的"強"引用打破,即將
john
設置爲
nil
。
john = nil
// Prints "John Appleseed is being deinitialized"
// Prints "Card #1234567890123456 is being deinitialized"
複製代碼
能夠看到
Customer
實例和
CreditCard
實例都被銷燬了。
john
被設置爲
nil
以後,就沒有"強"引用引用
Customer
實例,因此,
Customer
實例被釋放,也就沒有"強"引用引用
CreditCard
實例,所以
CreditCard
實例也被釋放。
以上例子證實,兩種方式均可以解決循環引用的問題,可是要注意它們使用的範圍。weak
修飾的變量能夠被設置爲nil
(引用的實例的生命週期短於另外一個實例),unowned
修飾的變量必需要指向一個實例(形成循環引用的兩實例的生命週期差很少長,不會出現一方被提早釋放的狀況),一旦它被釋放了,就千萬別再使用了。
Swift中的閉包是一種獨立的函數代碼塊,它能夠像一個類的實例同樣在代碼中賦值、調用和傳遞,也能夠被認爲某個匿名函數的實例,其實就是OC中的block。它和類同樣也是引用類型的,因此它的函數體中使用的引用都是"強"引用。
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> 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 being deinitialized")
}
}
複製代碼
上述例子中,閉包被賦值給asHTML
變量,因此閉包被HTMLElement
實例"強"引用,而閉包又捕獲(關於閉包捕獲變量,參考官方文檔Capturing Values)了HTMLElement
的實例中的text
和name
屬性,所以它又"強"引用HTMLElement
實例,這樣就形成了循環引用,由於text
屬性可能爲空,因此定義爲可選屬性。
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
複製代碼
咱們建立一個HTMLElement
實例,並將它賦值給paragraph
變量,而後訪問它的asHTML
屬性。此時的內存示例爲下圖,能夠看到HTMLElement
實例和閉包之間的循環引用。
paragraph
設置爲
nil
時,控制檯並無打印任何銷燬信息,由於循環引用。
上圖爲使用
Instruments分析獲得的循環引用以及形成的內存泄漏。
經過上文(三)的分析,咱們知道unowned
引用對實例的非擁有關係,所以,咱們能夠經過以下方式解決循環引用:
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
複製代碼
[unowned self] in
,這段代碼,表明閉包中的self
指針都被unowned
修飾。這樣就可使閉包對實例的"強"引用變成'unowned'引用,從而打破循環引用。 當HTML的element爲標題的時候,此時若是text
屬性爲空,咱們想返回一個默認的text做爲標題,而不是隻有<h/>
這種標籤。
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Prints "<h1>some default text</h1>"
複製代碼
這段代碼也會形成HTMLElement
對其自身的循環引用。咱們仍然可使用unowned
關鍵字打破循環引用:
heading.asHTML = {
[unowned heading] in
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
// Prints "<h1>some default text</h1>"
// Prints "h1 is being deinitialized"
複製代碼
unowned
會使閉包中對heading
的"強"都改成'unowned'引用。 或者,可使用weak
屬性打破循環引用:
weak var weakHeading = heading
heading.asHTML = {
return "<\(weakHeading!.name)>\(weakHeading!.text ?? defaultText)</\(weakHeading!.name)>"
}
// Prints "<h1>some default text</h1>"
//Prints "h1 is being deinitialized"
複製代碼
上文(三)中可知,weak
修飾的變量爲可選類型,並且,咱們對變量進行了一次賦值,就能夠確保weakHeading
指向heading
引用的實例,因此能夠放心的使用!
對它解包。 上面這段代碼一樣可使閉包對HTMLElement
實例的"強"引用變爲weak
引用,從而打破循環引用。 (ARC會自動回收不被使用的對象,因此不用手動將變量設置爲nil
)