Swift使用自動(dòng)引用計(jì)數(shù)

此文選自*標(biāo)哥博客*http://www.huangyibiao.com/archives/55

前言:

Swift使用自動(dòng)引用計(jì)數(shù)(ARC)機(jī)制來處理內(nèi)存。通常情況下韧拒,Swift內(nèi)存管理機(jī)制會(huì)自動(dòng)管理內(nèi)存掖看,無須我們考慮內(nèi)存的管理踱卵。ARC會(huì)在類的實(shí)例不再被使用(也就是沒有引用)時(shí)月趟,會(huì)自動(dòng)釋放其占用的內(nèi)存。

可是颜骤,在少數(shù)情況下,ARC需要更多地了解我們代碼之間的聯(lián)系捣卤,才能正確管理內(nèi)存复哆。本篇文章就這少數(shù)情況而討論和分析其應(yīng)用場景及如何更好地解決循環(huán)引用的問題欣喧。

注意:ARC僅應(yīng)用于類的實(shí)例。結(jié)構(gòu)體和枚舉類型是值類型梯找,不是引用類型唆阿,也不是通過引用的方式存儲(chǔ)和傳遞。

ARC的工作機(jī)制

創(chuàng)建類實(shí)例時(shí)锈锤,該實(shí)例就擁有了一塊內(nèi)存驯鳖,存儲(chǔ)相關(guān)信息,且會(huì)使該實(shí)例的引用計(jì)數(shù)+1久免,如下面的person實(shí)例當(dāng)前的引用計(jì)數(shù)就為1浅辙,當(dāng)前person對(duì)象只有一個(gè)引用。

class Person: NSObject {

deinit {

print("deinit")

}

}

var person: Person? = Person()

如果有多個(gè)引用阎姥,則該實(shí)例就不會(huì)被釋放记舆,看下面的mary和lili都引用了person,使其所指向的內(nèi)存引用計(jì)數(shù)+2:

var mary = person

var lili = person

現(xiàn)在我們將mary,lili設(shè)為nil,則person所指向的內(nèi)存塊引用計(jì)數(shù)-2呼巴,但是并不為0泽腮,因此還不會(huì)釋放。

mary = nil

lili = nil

現(xiàn)在我們再將person設(shè)為nil衣赶,會(huì)如何呢诊赊?自然會(huì)得到釋放了,因此就打印deinit出來了府瞄。

person = nil

為了確保使用中的實(shí)例不會(huì)被銷毀碧磅,ARC會(huì)跟蹤和計(jì)算每一個(gè)實(shí)例正在被多少屬性,常量和變量所引用遵馆。哪怕實(shí)例的引用數(shù)為1鲸郊,ARC都不會(huì)銷毀這個(gè)實(shí)例。

無論將實(shí)例賦值給屬性货邓、常量或變量严望,它們都會(huì)創(chuàng)建此實(shí)例的強(qiáng)引用。之所以稱之為“強(qiáng)”引用逻恐,是因?yàn)樗鼤?huì)將實(shí)例牢牢的保持住像吻,只要強(qiáng)引用還在,實(shí)例是不允許被銷毀的复隆。

場景一:類實(shí)例間的循環(huán)強(qiáng)引用

下面看一種這么一種情形:人可以有公寓拨匆,公寓可以屬于某個(gè)人。

class Person: NSObject {

var name: String

// 不一定人人都有公寓挽拂,因此設(shè)置為可選類型更合適

var apartment: Apartment?

init(name: String) {

self.name = name

}

deinit {

print("deinit person: \(self.name)")

}

}

class Apartment: NSObject {

var unit: String

// 公寓也可能還沒有擁有者惭每,設(shè)置為可選類型

// 注意,弱引用只能聲明為變量,不能聲明為常量

weak var owner: Person?

init(unit: String) {

self.unit = unit

}

deinit {

print("deinit apartment: \(self.unit)")

}

}

var mary: Person? = Person(name: "Mary")

var yaya: Apartment? = Apartment(unit: "yaya")

// 下面是建立兩者之間的關(guān)聯(lián)關(guān)系

mary?.apartment = yaya

yaya?.owner = mary

現(xiàn)在將這兩個(gè)對(duì)象的指向都修改為nil:

// 現(xiàn)在釋放台腥,然后就會(huì)得到打印結(jié)果:

// deinit person: Mary

// deinit apartment: yaya

mary = nil

yaya = nil

說明這兩個(gè)對(duì)象都得到正確的釋放了宏赘。這與我們預(yù)期的是一致的。

下面換一種寫法:如果我們將var apartment: Apartment?也聲明為weak呢黎侈?也就是改成weak var apartment: Apartment?察署,現(xiàn)在我們只將yaya改為nil會(huì)如何呢?

yaya = nil

print(mary?.apartment?.unit)

當(dāng)apartment聲明為var apartment: Apartment?峻汉,其打印結(jié)果為Optional("yaya")

當(dāng)apartment聲明為weak var apartment: Apartment?贴汪,其打印結(jié)果為nil。

雖然這么做兩者都得到釋放休吠,但是這跟我們預(yù)期的結(jié)果不一致扳埂。這是因?yàn)殡p方都是弱引用,當(dāng)yaya =nil時(shí)瘤礁,其引用計(jì)數(shù)值為0阳懂,因此會(huì)被釋放。這時(shí)候再去訪問柜思,就不存在了岩调。如果其類型不是可選類型,釋放后再訪問酝蜒,會(huì)導(dǎo)致crash的。

如果兩個(gè)類實(shí)例之間都是可選類型矾湃,使用weak關(guān)鍵字來處理弱引用更好亡脑,但應(yīng)該是一強(qiáng)一弱引用。

場景二:類實(shí)例與屬性的循環(huán)強(qiáng)引用

下面的例子定義了兩個(gè)類邀跃,Customer和CreditCard霉咨,模擬了銀行客戶和客戶的信用卡。這兩個(gè)類中拍屑,每一個(gè)都將另外一個(gè)類的實(shí)例作為自身的屬性途戒。這種關(guān)系可能會(huì)造成循環(huán)強(qiáng)引用

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關(guān)鍵字聲明為無主引用,可以聲明為變量或者常量僵驰,但是不能聲明為可選類型

// 對(duì)于無主引用喷斋,永遠(yuǎn)都是有值的,因此不可聲明為可選類型蒜茴。

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? = Customer(name: "John")

john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

現(xiàn)在我們將john置為nil星爪,其結(jié)果如何呢?

// 打印結(jié)果:

// John is being deinitialized

// Card #1234567890123456 is being deinitialized

john = nil

根據(jù)打印結(jié)果粉私,我們可以看到這兩個(gè)實(shí)例都得到正確的釋放了顽腾。使用unowned關(guān)鍵字聲明為無主引用,可以聲明為變量或者常量诺核,但是不能聲明為可選類型抄肖。對(duì)于無主引用久信,永遠(yuǎn)都是有值的,因此不可聲明為可選類型漓摩。

注意: 如果試圖在實(shí)例被銷毀后裙士,訪問該實(shí)例的無主引用,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤幌甘。使用無主引用潮售,你必須確保引用始終指向一個(gè)未銷毀的實(shí)例。還需要注意的是,如果試圖訪問實(shí)例已經(jīng)被銷毀的無主引用锅风,Swift確保程序會(huì)直接崩潰酥诽,而不會(huì)發(fā)生無法預(yù)期的行為。

場景三:閉包引起的循環(huán)強(qiáng)引用

下面我們定義DemoView和DemoController類皱埠,前者有一個(gè)閉包作為屬性肮帐,用于反向傳值到后者。

typealias DemoClosure = (isSelected: Bool) ->Void

class DemoView: UIView {

var closure: DemoClosure?

func callback(selected: Bool) {

if let callback = closure {

callback(isSelected: selected)

}

}

deinit {

print("demo view deinit")

}

}

class DemoController: UIViewController {

var demoView: DemoView?

var name: String = "DemoControllerName"

override func viewDidLoad() {

super.viewDidLoad()

demoView = DemoView()

self.view.addSubview(demoView!)

// 注意边器,這里使用了[unowned self]無主引用

demoView?.closure = { [unowned self](isSelcted: Bool) in

// 閉包內(nèi)训枢,強(qiáng)引用了self,也就是DemoController

print("\(self.name)")

}

// 測試調(diào)用

self.passToDmeoView(true)

}

func passToDmeoView(selected: Bool) {

demoView?.callback(selected)

}

deinit {

print("DemoController deinit")

}

}

對(duì)比一下添加了[unowned self]與未添加的打印結(jié)果:

不添加[unowned self]忘巧,直接使用self.name時(shí)恒界,當(dāng)返回上一個(gè)界面時(shí),由于循環(huán)強(qiáng)引用砚嘴,DemoController對(duì)象和DemoView者得不到釋放十酣。

添加了[unowned self]后,打印結(jié)果說明可以得到釋放际长,如下:

DemoController deinit

demo view deinit

我們在使用閉包時(shí)耸采,一定要注意循環(huán)強(qiáng)引用的問題,否則很容易千萬內(nèi)存泄露工育。除了使用無主引用之外虾宇,還有weak關(guān)鍵字來聲明使用弱使用,比如:

lazy var closure: (Int, String) -> String = {

[unowned self, weak delegate = self.delegate] (index: Int, title: String) -> String in

// closure body goes here

print("\(self.name)")

delegate.callBack(title)

}

為了防止循環(huán)強(qiáng)引用如绸,對(duì)于weak delegate = self.delegate!也需要使用弱引用嘱朽。

對(duì)于閉包內(nèi)的引用,何時(shí)使用弱引用怔接,何時(shí)使用無主引用呢燥翅?

在閉包和捕獲的實(shí)例總是互相引用時(shí)并且總是同時(shí)銷毀時(shí),將閉包內(nèi)的捕獲定義為無主引用蜕提,如上面的closure閉包中的self與closure問題互相引用且同時(shí)銷毀森书。

在被捕獲的引用可能會(huì)變?yōu)閚il時(shí),將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型凛膏,并且當(dāng)引用的實(shí)例被銷毀后杨名,弱引用的值會(huì)自動(dòng)置為nil。

注意:如果被捕獲的引用絕對(duì)不會(huì)變?yōu)閚il猖毫,應(yīng)該用無主引用台谍,而不是弱引用。如果上面的self.delegate不是可選類型吁断,那么其值會(huì)一直存在趁蕊,我們就不應(yīng)該使用weak引用,而是使用unowned引用仔役。

總結(jié)

解決循環(huán)強(qiáng)引用的類型有兩種:

使用weak關(guān)鍵字聲明為弱引用掷伙。使用weak的弱引用只能聲明為變量(不能聲明為常量),表明其值能在運(yùn)行時(shí)被修改又兵。

使用unowned關(guān)鍵聲明為無主引用任柜。無主引用總是被定義為非可選類型,因?yàn)槠溆肋h(yuǎn)是有值的沛厨,可聲明為常量或變量宙地,值不能為nil。

常見的循環(huán)強(qiáng)引用場景有:類實(shí)例與類屬性間循環(huán)強(qiáng)引用逆皮、類實(shí)例與類實(shí)例間循環(huán)強(qiáng)引用绰精、類與閉包循環(huán)強(qiáng)引用等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臼氨,一起剝皮案震驚了整個(gè)濱河市相味,隨后出現(xiàn)的幾起案子特幔,更是在濱河造成了極大的恐慌,老刑警劉巖辰企,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件风纠,死亡現(xiàn)場離奇詭異况鸣,居然都是意外死亡牢贸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門镐捧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潜索,“玉大人,你說我怎么就攤上這事懂酱≈裣埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵列牺,是天一觀的道長整陌。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么泌辫? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任随夸,我火速辦了婚禮,結(jié)果婚禮上震放,老公的妹妹穿的比我還像新娘宾毒。我一直安慰自己,他們只是感情好殿遂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布诈铛。 她就那樣靜靜地躺著,像睡著了一般墨礁。 火紅的嫁衣襯著肌膚如雪幢竹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天饵溅,我揣著相機(jī)與錄音妨退,去河邊找鬼。 笑死蜕企,一個(gè)胖子當(dāng)著我的面吹牛咬荷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轻掩,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼幸乒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唇牧?” 一聲冷哼從身側(cè)響起罕扎,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丐重,沒想到半個(gè)月后腔召,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扮惦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年臀蛛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片崖蜜。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浊仆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豫领,到底是詐尸還是另有隱情抡柿,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布等恐,位于F島的核電站洲劣,受9級(jí)特大地震影響备蚓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜囱稽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一星著、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粗悯,春花似錦虚循、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衫哥,卻和暖如春茎刚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撤逢。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工膛锭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚊荣。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓初狰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親互例。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奢入,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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