// ARC
//這一章我寫的不好称诗,需要就去看原書
//“Swift 使用自動引用計數(shù)(ARC)機(jī)制來跟蹤和管理你的應(yīng)用程序的內(nèi)存”
//“通常情況下纯赎,Swift 內(nèi)存管理機(jī)制會一直起作用痢畜,你無須自己來考慮內(nèi)存的管理。ARC 會在類的實例不再被使用時,自動釋放其占用的內(nèi)存叛氨《樽校”
//“然而在少數(shù)情況下擂橘,為了能幫助你管理內(nèi)存,ARC 需要更多的摩骨,代碼之間關(guān)系的信息通贞。本章描述了這些情況,并且為你示范怎樣才能使 ARC 來管理你的應(yīng)用程序的所有內(nèi)存恼五。在 Swift 使用 ARC 與在 Obejctive-C 中使用 ARC 非常類似”
//“注意 引用計數(shù)僅僅應(yīng)用于類的實例昌罩。結(jié)構(gòu)體和枚舉類型是值類型,不是引用類型灾馒,也不是通過引用的方式存儲和傳遞茎用。”
//1. 自動引用計數(shù)的工作機(jī)制
//“ARC 會分配一塊內(nèi)存來儲存該實例信息睬罗。內(nèi)存中會包含實例的類型信息轨功,以及這個實例所有相關(guān)的存儲型屬性的值∪荽铮”
//“當(dāng)實例不再被使用時古涧,ARC 釋放實例所占用的內(nèi)存,并讓釋放的內(nèi)存能挪作他用董饰。這確保了不再被使用的實例蒿褂,不會一直占用內(nèi)存空間”
//“當(dāng) ARC 收回和釋放了正在被使用中的實例,該實例的屬性和方法將不能再被訪問和調(diào)用卒暂。實際上啄栓,如果你試圖訪問這個實例,你的應(yīng)用程序很可能會崩潰”
//“為了確保使用中的實例不會被銷毀也祠,ARC 會跟蹤和計算每一個實例正在被多少屬性昙楚,常量和變量所引用。哪怕實例的引用數(shù)為1诈嘿,ARC都不會銷毀這個實例”
//“為了使上述成為可能堪旧,無論你將實例賦值給屬性削葱、常量或變量,它們都會創(chuàng)建此實例的強(qiáng)引用淳梦。之所以稱之為“強(qiáng)”引用析砸,是因為它會將實例牢牢地保持住,只要強(qiáng)引用還在爆袍,實例是不允許被銷毀的”
//2.自動引用計數(shù)實踐
class Person{
let name:String
var apartment : Apartment?
init(name:String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1:Person?
var reference2:Person?
var reference3:Person?
reference1 = Person(name:"john titol")
//打印 john titol is being initialized
reference2 = reference1
reference3 = reference1
//“現(xiàn)在這一個Person實例已經(jīng)有三個強(qiáng)引用了”
reference1 = nil
reference2 = nil
//“只留下一個強(qiáng)引用首繁,Person實例不會被銷毀”
reference3 = nil
//打印 john titol is being deinitialized
//3.類實例之間的循環(huán)強(qiáng)引用
//“我們可能會寫出一個類實例的強(qiáng)引用數(shù)永遠(yuǎn)不能變成0的代碼。如果兩個類實例互相持有對方的強(qiáng)引用陨囊,因而每個實例都讓對方一直存在弦疮,就是這種情況。這就是所謂的循環(huán)強(qiáng)引用蜘醋⌒踩”
//“你可以通過定義類之間的關(guān)系為弱引用或無主引用,以替代強(qiáng)引用压语,從而解決循環(huán)強(qiáng)引用的問題”
class Apartment{
let unit:String
init(unit:String) {
self.unit = unit
}
var tenant:Person?
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john:Person?
var unit4A:Apartment?
john = Person(name:"John applesseed")
unit4A = Apartment(unit:"4A")
john!.apartment = unit4A
unit4A!.tenant = John
//“這兩個實例關(guān)聯(lián)后會產(chǎn)生一個循環(huán)強(qiáng)引用啸罢。Person實例現(xiàn)在有了一個指向Apartment實例的強(qiáng)引用,而Apartment實例也有了一個指”“向Person實例的強(qiáng)引用胎食。因此伺糠,當(dāng)你斷開john和unit4A變量所持有的強(qiáng)引用時,引用計數(shù)并不會降為0斥季,實例也不會被 ARC 銷毀:”
john = nil
unit4A = nil
//“當(dāng)你把這兩個變量設(shè)為nil時,沒有任何一個析構(gòu)函數(shù)被調(diào)用累驮。循環(huán)強(qiáng)引用會一直阻止Person和Apartment類實例的銷毀酣倾,這就在你的應(yīng)用程序中造成了內(nèi)存泄漏“ǎ”
//4. 解決實例之間的循環(huán)強(qiáng)引用
//“Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環(huán)強(qiáng)引用問題:弱引用(weak reference)和無主引用(unowned reference)”
//“弱引用和無主引用允許循環(huán)引用中的一個實例引用而另外一個實例不保持強(qiáng)引用躁锡。這樣實例能夠互相引用而不產(chǎn)生循環(huán)強(qiáng)引用≈檬蹋”
//“當(dāng)其他的實例有更短的生命周期時映之,使用弱引用,也就是說蜡坊,當(dāng)其他實例析構(gòu)在先時杠输。“當(dāng)其他實例有相同的或者更長生命周期時秕衙,請使用無主引用.在上面公寓的例子中蠢甲,很顯然一個公寓在它的生命周期內(nèi)會在某個時間段沒有它的主人,所以一個弱引用就加在公寓類里面据忘,避免循環(huán)引用鹦牛。
//4.1 弱引用
// “弱引用不會對其引用的實例保持強(qiáng)引用搞糕,因而不會阻止 ARC 銷毀被引用的實例。這個特性阻止了引用變?yōu)檠h(huán)強(qiáng)引用曼追。聲明屬性或者變量時窍仰,在前面加上weak關(guān)鍵字表明這是一個弱引用±袷猓”
//“因為弱引用不會保持所引用的實例驹吮,即使引用存在,實例也有可能被銷毀膏燕。因此钥屈,ARC 會在引用的實例被銷毀后自動將其賦值為nil。并且因為弱引用可以允許它們的值在運行時被賦值為nil坝辫,所以它們會被定義為可選類型變量篷就,而不是常量〗Γ”
//“你可以像其他可選值一樣竭业,檢查弱引用的值是否存在,你將永遠(yuǎn)不會訪問已銷毀的實例的引用”
//“注意 當(dāng) ARC 設(shè)置弱引用為nil時及舍,屬性觀察不會被觸發(fā)未辆。”
//“注意 在使用垃圾收集的系統(tǒng)里锯玛,弱指針有時用來實現(xiàn)簡單的緩沖機(jī)制咐柜,因為沒有強(qiáng)引用的對象只會在內(nèi)存壓力觸發(fā)垃圾收集時才被銷毀。但是在 ARC 中攘残,一旦值的最后一個強(qiáng)引用被移除拙友,就會被立即銷毀,這導(dǎo)致弱引用并不適合上面的用途”
//4.2 無主引用
//“和弱引用類似歼郭,無主引用不會牢牢保持住引用的實例遗契。和弱引用不同的是,無主引用在其他實例有相同或者更長的生命周期時使用病曾。你可以在聲明屬性或者變量時牍蜂,在前面加上關(guān)鍵字unowned表示這是一個無主引用”
//“無主引用通常都被期望擁有值。不過 ARC 無法在實例被銷毀后將無主引用設(shè)為nil泰涂,因為非可選類型的變量不允許被賦值為nil鲫竞。”
//“重要 使用無主引用负敏,你必須確保引用始終指向一個未銷毀的實例贡茅。如果你試圖在實例被銷毀后,訪問該實例的無主引用,會觸發(fā)運行時錯誤顶考×藁梗”
class Customer{
let name:String
var card:CreditCard?
init(name:String) {
self.name = name
}
deinit {
print("\(name) is being deinitializd")
}
}
class CreditCard{
let number:UInt64
unowned let customer:Customer
init(number:UInt64,customer:Customer) {
self.number = number
self.customer = customer
}
deinit {
print("card # \(self.number) is being deinitializd")
}
}
//“Customer和CreditCard之間的關(guān)系與前面弱引用例子中Apartment和Person的關(guān)系略微不同。在這個數(shù)據(jù)模型中驹沿,一個客戶可能有或者沒有信用卡艘策,但是一張信用卡總是關(guān)聯(lián)著一個客戶。為了表示這種關(guān)系渊季,Customer類有一個可選類型的card屬性朋蔫,但是CreditCard類有一個非可選類型的customer屬性∪春海”
var jack:Customer?
jack = Customer(name:"jack caption")
jack!.card = CreditCard(number:1234_5678_9999,customer:jack!)
jack = nil
//打印 jack caption is being deinitializd
// card # 123456789999 is being deinitializd
//“你可以通過unowned(unsafe)來聲明不安全無主引用驯妄。如果你試圖在實例被銷毀后,訪問該實例的不安全無主引用合砂,你的程序會嘗試訪問該實例之前所在的內(nèi)存地址青扔,這是一個不安全的操作”
//個人看法:下邊這個有點麻煩,搞不懂了可以去看書翩伪。
//4.3 無主引用以及隱式解析可選屬性
//“上面弱引用和無主引用的例子涵蓋了兩種常用的需要打破循環(huán)強(qiáng)引用的場景微猖。
//Person和Apartment的例子展示了兩個屬性的值都允許為nil,并會潛在的產(chǎn)生循環(huán)強(qiáng)引用缘屹。這種場景最適合用弱引用來解決凛剥。”
//“Customer和CreditCard的例子展示了一個屬性的值允許為nil轻姿,而另一個屬性的值不允許為nil犁珠,這也可能會產(chǎn)生循環(huán)強(qiáng)引用。這種場景最適合通過無主引用來解決互亮∶ぴ鳎”
//“存在著第三種場景,在這種場景中胳挎,兩個屬性都必須有值,并且初始化完成后永遠(yuǎn)不會為nil溺森。在這種場景中慕爬,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性”
//5. 閉包引起的循環(huán)強(qiáng)引用
//“循環(huán)強(qiáng)引用還會發(fā)生在當(dāng)你將一個閉包賦值給類實例的某個屬性屏积,并且這個閉包體中又使用了這個類實例時医窿。這個閉包體中可能訪問了實例的某個屬性,例如self.someProperty炊林,或者閉包中調(diào)用了實例的某個方法姥卢,例如self.someMethod()。這兩種情況都導(dǎo)致了閉包“捕獲”self,從而產(chǎn)生了循環(huán)強(qiáng)引用独榴∩妫”
//“循環(huán)強(qiáng)引用的產(chǎn)生,是因為閉包和類相似棺榔,都是引用類型瓶堕。當(dāng)你把一個閉包賦值給某個屬性時,你是將這個閉包的引用賦值給了屬性症歇。實質(zhì)上郎笆,這跟之前的問題是一樣的——兩個強(qiáng)引用讓彼此一直有效。但是忘晤,和兩個類實例不同宛蚓,這次一個是類實例,另一個是閉包设塔∑嗬簦”
//“Swift 提供了一種優(yōu)雅的方法來解決這個問題,稱之為閉包捕獲列表(closure capture list)”
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")
}
}
let heading = HtmlElement(name:"h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
//打印 <h1>some default text </h1>
//“注意 asHTML聲明為lazy屬性壹置,因為只有當(dāng)元素確實需要被處理為 HTML 輸出的字符串時竞思,才需要使用asHTML。也就是說钞护,在默認(rèn)的閉包中可以使用self盖喷,因為只有當(dāng)初始化完成以及self確實存在后,才能訪問lazy屬性难咕】问幔”
var parragraph:HtmlElement? = HtmlElement(name:"p",text:"hello world")
print(parragraph!.asHTML())
//“上面寫的HTMLElement類產(chǎn)生了類實例和作為asHTML默認(rèn)值的閉包之間的循環(huán)強(qiáng)引用∮嗟瑁”
//“實例的asHTML屬性持有閉包的強(qiáng)引用暮刃。但是,閉包在其閉包體內(nèi)使用了self(引用了self.name和self.text)爆土,因此閉包捕獲了self椭懊,這意味著閉包又反過來持有了HTMLElement實例的強(qiáng)引用。這樣兩個對象就產(chǎn)生了循環(huán)強(qiáng)引用步势⊙踱”
//“注意 雖然閉包多次使用了self,它只捕獲HTMLElement實例的一個強(qiáng)引用坏瘩≈迅В”
parragraph = nil
//析構(gòu)函數(shù)沒有打印,實例未被銷毀倔矾。
//5. 解決閉包引起的循環(huán)強(qiáng)引用
//“在定義閉包時同時定義捕獲列表作為閉包的一部分妄均,通過這種方式可以解決閉包和類實例之間的循環(huán)強(qiáng)引用柱锹。捕獲列表定義了閉包體內(nèi)捕獲一個或者多個引用類型的規(guī)則”
//“注意 Swift 有如下要求:只要在閉包內(nèi)使用self的成員,就要用self.someProperty或者self.someMethod()(而不只是someProperty或someMethod())。這提醒你可能會一不小心就捕獲了self∮止伲”
//5.1 定義捕獲列表
class SomeClass{
var delegate : HtmlElement?
lazy var someClosure:(Int,String)->String = {
[unowned self,weak delegate = self.delegate!](index:Int,stringToProcess:String)->String in
// 這里是閉包的函數(shù)體。
return "\(delegate?.name)"
}
}
//“在閉包和捕獲的實例總是互相引用并且總是同時銷毀時匹层,將閉包內(nèi)的捕獲定義為無主引用。
//相反的锌蓄,在被捕獲的引用可能會變?yōu)閚il時升筏,將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型瘸爽,并且當(dāng)引用的實例被銷毀后您访,弱引用的值會自動置為nil。這使我們可以在閉包體內(nèi)檢查它們是否存在剪决×橥簦”
//“注意 如果被捕獲的引用絕對不會變?yōu)閚il,應(yīng)該用無主引用柑潦,而不是弱引用享言。”
class TestClass {
let name : String
let text : String?
lazy var testClosure : ()->String = {
[unowned self] in
if let text = self.text {
return "\(text)"
}else{
return "\(self.name)"
}
}
init(name:String,text:String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var testClass : TestClass? = TestClass(name:"p",text:"hello world")
print(testClass!.testClosure())
//打印 hello world
testClass = nil
// 打印 p is being deinitialized