Swift-ARC(Automatic Reference Counting)

Swift管理內(nèi)存方式和OC極其相似,都是采用自動引用計數(shù) ARC機(jī)制來跟蹤和管理。
ACR 會在實例不再使用的情況下自己釋放其占用的內(nèi)存空間吊输,所以通常情況下不需要我們自己來手動釋放环鲤。
引用計數(shù)應(yīng)用于類的實例
(注意:結(jié)構(gòu)體枚舉值類型So結(jié)構(gòu)體和枚舉不是通用引用計數(shù)來管理內(nèi)存的等脂。)

本章結(jié)合了Swift 官方文檔裁奇。挑一些主要點(diǎn)進(jìn)行描述Swift的引用計數(shù)機(jī)制
章節(jié)如下:

  1. 自動引用計數(shù)工作機(jī)制
  2. 自動引用計數(shù)實踐
  3. 類實例之間的循環(huán)強(qiáng)引用
  4. 解決實例之間的循環(huán)強(qiáng)引用
  5. 閉包(即OC-Block)引起的循環(huán)強(qiáng)引用
  6. 解決閉包引起的循環(huán)強(qiáng)引用

1.自動引用計數(shù)工作機(jī)制

簡單說就是每當(dāng)我們創(chuàng)建一個實例的時候ARC都會開辟一塊內(nèi)存空間.供這個實例使用稚补,內(nèi)存中會包含這個實例及這個實例的類型信息。無論這個實例是被賦值為屬性,變量或常量框喳。直到我們不再使用這個實例,ARC會自動釋放此實例,并將這塊內(nèi)存空間挪它使用厦坛。其機(jī)制和OC一樣五垮,若訪問被釋放的實例則Crash。只要這個實例仍存在,強(qiáng)引用就伴隨其一直存在杜秸,就可訪問放仗。

2.自動引用計數(shù)實踐

若上面這段富有理論性的話大家不太明白,請注意看這個栗子

class Person {
    let name: String
    init(name: String) {//init 構(gòu)造函數(shù)撬碟。"人"這個實例對象,具有一個自己的屬性"名字"诞挨,人被初始化了,其名字也同時被初始化。
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {//人掛了呢蛤,其屬性肯定也掛了(”deinit析構(gòu)函數(shù)“ 等同OC dealloc )
        print("\(name) is being deinitialized")
    }
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
/*我們有三個為Person類型的變量. 注意問號"?". 代表可選類型惶傻,也就是,可為空其障。
所以银室,這時候還沒有強(qiáng)引用,這三個變量的初始值都為nil.還沒有被創(chuàng)建.*/
/*
接下來我們創(chuàng)建Person類實例對象,給其中一個變量賦值.注意:這時候reference1到Person類實例之間建立強(qiáng)引用蜈敢,所以ARC保證Person一直存在內(nèi)存中辜荠。可以看到打印抓狭。
*/
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized
/*
此時,我們將Person也賦值給其它兩個變量,這時候Person強(qiáng)引用加二
*/
reference2 = reference1
reference3 = reference1
/*
此時我們再將兩個變量賦值為nil.則Person就少去兩個強(qiáng)引用伯病。注意:此時Person還是存在的哈,因為還存在一個強(qiáng)引用,還有一個變量是它否过。
*/
reference1 = nil
reference2 = nil
/*
好了午笛,我們可手動銷毀最后一個強(qiáng)引用。這時候ARC會釋放掉Person,可看打印叠纹。
*/
reference3 = nil
// 打印 “John Appleseed is being deinitialized

3.類實例之間的循環(huán)強(qiáng)引用

栗子
有兩個類季研,Person Apartment.
Person具有實例屬性為apartment,apartment是可選的,所以初始值為nil,所以這時候Preson 并不會產(chǎn)生強(qiáng)引用加一。這塊兒注意誉察,這是一會演示產(chǎn)生循環(huán)強(qiáng)引用的關(guān)鍵.
Apartment.同Person一樣也有個初始化為nil的實例屬性Person

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") }
}

現(xiàn)在我在另一個類中寫兩個實例屬性与涡,分別為PersonApartment的初始值為nil的可選屬性john,unit4.

var john: Person?
var unit4A: Apartment?

現(xiàn)在創(chuàng)建特定的PersonApartment實例,并將其賦值給john和unit4A變量
注意了:這時候johnPerson之間建立了強(qiáng)引用.unit4AApartment之間也建立了強(qiáng)引用.

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

現(xiàn)在我將unit4A賦值給Johnapartment持偏,將john賦值給unit4Atenant
注意了:關(guān)鍵就在這兒了驼卖,這時候johnunit4A 的內(nèi)部實例屬性是有強(qiáng)引用的.所以在下面我將johnunit4A,手動制空,其會被ARC釋放嗎?析構(gòu)函數(shù)deinit會被調(diào)用嗎? 答案是肯定不會鸿秆,這就造成了類實例之間的循環(huán)強(qiáng)引用.
為什么?雖然我將johnjohn手動制空,釋放了一個強(qiáng)引用酌畜,但是其內(nèi)部是不是還有個實力屬性擁有強(qiáng)引用哇?卿叽。這就造成了內(nèi)存泄漏,不得了了桥胞。

john!.apartment = unit4A
unit4A!.tenant = john
john = nil
unit4A = nil

那怎么解決呢? 請往下看

4.解決實例之間的循環(huán)強(qiáng)引用

Swift 提供了兩種方法來解決使用類的屬性時,所遇到的循環(huán)強(qiáng)引用問題.
弱引用weak reference, 無主引用unowned reference
當(dāng)其他的實例有更短的生命周期使用弱引用考婴,反之使用無主引用.

  • ****弱引用****
    關(guān)鍵字weak
    當(dāng)其他的實例有更短的生命周期時贩虾,使用弱引用,也就是說沥阱,當(dāng)其他實例析構(gòu)在先時缎罢。
    修飾可為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") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 打印 “John Appleseed is being deinitialized
unit4A = nil
// 打印 “Apartment 4A is being deinitialized


john為nil時考杉,John再不存在強(qiáng)引用了,徹底掛了

unit4A為nil,unit4A也掛了

看懂沒? 沒錯,就是weak 關(guān)鍵字修飾Apartment類的實例屬性Person
ARC會在實例的引用被銷毀的時候自動將其被weak修飾的實例屬性設(shè)置為nil策精,所以當(dāng)john掛了,它也就徹底掛了,弱引用不會對其引用的實例進(jìn)行強(qiáng)引用!
注意 當(dāng)ARC設(shè)置弱引用沒nil時崇棠,它的屬性觀察器是不會被觸發(fā)的.
PS:ApartmentPerson的例子很好的演示了咽袜,兩個類都擁有一個可為nil的可選屬性。并且在另一個類中易茬,這兩個類的實例屬性相互持有酬蹋。這就產(chǎn)生的潛在的循環(huán)強(qiáng)引用及老,這時候我們用弱引用weak再適合不過了.

  • ****無主引用****
    在屬性或變量前面加一個關(guān)鍵字unowned表示這是一個無主引用。
    無主引用和弱引用一樣不會牢牢保持一個實例的引用范抓。
    不同就是ARC不會在實例銷毀后將其設(shè)置為nil骄恶。因為其是非可選 的類型的變量,所以不允許被設(shè)置為nil
    直接看栗子
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") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized

兩個類,Customer信用卡客戶,和CreditCard信用卡
信用卡客戶:有個名字是肯定的let name: String,信用卡不一定有var card: CreditCard?
信用卡:卡號和卡持有者匕垫,是肯定有的,所以都用let修飾.注意正是因為這個用無主引用關(guān)鍵字unowned修飾的實例對象Customer卡片持有者僧鲁,所以之后相互引用才不會導(dǎo)致循環(huán)強(qiáng)引用.然后我們必須通過卡號和持卡者創(chuàng)建信用卡.
看圖示

實例對象為Customer的變量john創(chuàng)建時,Customer實例持有對CreditCard實例的強(qiáng)引用。 CreditCard實例創(chuàng)建的時候象泵,對Customer是無主引用的

所以寞秃,當(dāng)我們手動將John制空,會斷開John持有的強(qiáng)引用。

Customer,和CreditCard,Customer一個屬性值可為nil偶惠,一個屬性值不可為nil春寿,然后在里一個類中,這兩個類相互引用忽孽,產(chǎn)生強(qiáng)引用绑改。這種情況無主引用最合適不過了。

  • ****無主引用以及隱式解析可選屬性****
    栗子
class Country {
    let name: String
    var 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
    }
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa

感嘆號(City!)的方式兄一,將Country的capitalCity屬性聲明為隱式解析可選類型的屬性厘线。這意味著像其他可選類型一樣,capitalCity屬性的默認(rèn)值為nil出革,但是不需要展開它的值就能訪問它造壮。
capitalCity屬性在初始化完成后,能像非可選值一樣使用和存取骂束,同時還避免了循環(huán)強(qiáng)引用耳璧。

5. 閉包引起的循環(huán)引用

閉包中會如何產(chǎn)生循環(huán)強(qiáng)引用?
閉包中引用了self,比如self.someProperty,self.someMethod
為什么?
這是因為閉包和類相似都是屬于引用類型。當(dāng)我們把閉包賦值給類的某個屬性展箱,其實是把這個引用賦值給這個屬性楞抡。和上面類實例之前的循環(huán)強(qiáng)引用一樣,會產(chǎn)生兩個強(qiáng)引用一直存在析藕。是不是不太好理解?來我們看栗子

不整沒用的凳厢,直接看圖

在上圖例子相信大家就會明白账胧,什么是閉包引起的循環(huán)強(qiáng)引用,以及怎樣解決閉包引起的循環(huán)強(qiáng)引用先紫。
有的同學(xué)可能不太理解治泥,我靠,這是什么寫法遮精?這是Swift的捕獲列表居夹。
捕獲列表:捕獲列表中的每一項都由一對元素組成败潦,一個元素是weak或unowned關(guān)鍵字,另一個元素是類實例的引用(例如self)或初始化過的變量(如delegate = self.delegate!)


****總結(jié):****
ARC自動幫我們管理內(nèi)存准脂,也是采用垃圾回收機(jī)制劫扒。
切記:
兩個類的實例相互保持對方的強(qiáng)引用時會產(chǎn)生循環(huán)強(qiáng)引用。閉包會產(chǎn)生循環(huán)強(qiáng)引用,尤其Rx,RAC的回調(diào)實現(xiàn)中很敏感狸膏。
開發(fā)中通常我們不會手動將實例對象制空沟饥。若制空,再訪問那么湾戳,你懂得贤旷。
主要說下循環(huán)強(qiáng)引用:
循環(huán)強(qiáng)引用實質(zhì)上說白了就是,這個類最終沒有釋放最終沒走deinit方法砾脑。為什么幼驶?有可能是你的類與實力之間產(chǎn)生的循環(huán)強(qiáng)引用,有可能是你的閉包引起的循環(huán)強(qiáng)引用韧衣。
怎么解決盅藻?大家可以參考上面的例子。在實際開發(fā)中呢汹族,我們最常見的就是閉包引起的循環(huán)引用萧求。這里總結(jié)下解決辦法

//捕獲列表:
        object.block = { [weak self] in // 弱引用
           self.xxx
        }
        object.block = { [unowned self] in //無主引用
           self.xxx
        }
//若引用修飾self:
        weak var ws = self
        object.block = { [unowned self] in
           ws.xxx
        }

weak和unowned區(qū)別:
unowned 更像OC的 unsafe_unretained ,而 weak 就是以前的 weak 顶瞒。
unowned設(shè)置以后即使它原來引用的內(nèi)容已經(jīng)被釋放了夸政,它仍然會保持對被已經(jīng)釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值榴徐,也不會被指向 nil 守问。如果你嘗試調(diào)用這個引用的方法或者訪問成員屬性的話,程序就會崩潰坑资。
weak 在引用的內(nèi)容被釋放后耗帕,標(biāo)記為 weak 的成員將會自動地變成 nil (所以被標(biāo)記為 weak 的變量一定需要是 Optional 值)。
關(guān)于兩者使用的選擇袱贮,存在被釋放的可能仿便,那就選擇用 weak 。開發(fā)中用weak 會多一些.

題外話:最近忙了一陣兒攒巍,回頭續(xù)寫嗽仪,寫了一半的文章,發(fā)現(xiàn)太不干了柒莉。所以以后的文章會盡量寫一寫干的東西闻坚。寫這么大一篇文章,我覺得直接看總結(jié)這部分就好了兢孝。??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窿凤,一起剝皮案震驚了整個濱河市仅偎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雳殊,老刑警劉巖橘沥,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異相种,居然都是意外死亡威恼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門寝并,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箫措,“玉大人,你說我怎么就攤上這事衬潦〗锫” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵镀岛,是天一觀的道長弦牡。 經(jīng)常有香客問我,道長漂羊,這世上最難降的妖魔是什么驾锰? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮走越,結(jié)果婚禮上椭豫,老公的妹妹穿的比我還像新娘。我一直安慰自己旨指,他們只是感情好赏酥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谆构,像睡著了一般裸扶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搬素,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天呵晨,我揣著相機(jī)與錄音,去河邊找鬼熬尺。 笑死何荚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猪杭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼妥衣,長吁一口氣:“原來是場噩夢啊……” “哼皂吮!你這毒婦竟也來了戒傻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蜂筹,失蹤者是張志新(化名)和其女友劉穎需纳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年鸠澈,在試婚紗的時候發(fā)現(xiàn)自己被綠了幕垦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡柿究,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妙蔗,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布疆瑰,位于F島的核電站眉反,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏穆役。R本人自食惡果不足惜寸五,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耿币。 院中可真熱鬧梳杏,春花似錦、人聲如沸掰读。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹈集。三九已至烁试,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拢肆,已是汗流浹背减响。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郭怪,地道東北人支示。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鄙才,于是被迫代替她去往敵國和親颂鸿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 126.析構(gòu)器 在一個類實例銷毀前,一個析構(gòu)器會立即調(diào)用攒庵。使用deinit 關(guān)鍵字來表示析構(gòu)器, 跟構(gòu)造器寫法類似...
    無灃閱讀 781評論 0 4
  • Swift 使用自動引用計數(shù)(ARC)機(jī)制來跟蹤和管理你的應(yīng)用程序的內(nèi)存嘴纺。通常情況下败晴,Swift 內(nèi)存管理機(jī)制會一...
    莽原奔馬668閱讀 181評論 0 1
  • 本章將會介紹 自動引用計數(shù)的工作機(jī)制自動引用計數(shù)實踐類實例之間的循環(huán)強(qiáng)引用解決實例之間的循環(huán)強(qiáng)引用閉包引起的循環(huán)強(qiáng)...
    寒橋閱讀 893評論 0 0
  • 1.簡介 Swift 使用自動引用計數(shù)(ARC)機(jī)制來跟蹤和管理內(nèi)存闲擦。ARC 會在類的實例不再被使用時慢味,自動釋放其...
    sudhengshi閱讀 240評論 0 1
  • 在廣州生活四年了,從大學(xué)墅冷,到留城工作纯路,已經(jīng)漸漸融入了這里。它有很大的包容性俺榆,不管你來自何方感昼,只要努力,就可以...
    夜子子閱讀 208評論 0 0