swift內(nèi)存管理

Swift使用自動引用計數(shù)(ARC)機制來處理內(nèi)存咬荷。通常情況下,Swift內(nèi)存管理機制會自動管理內(nèi)存白群,無須我們考慮內(nèi)存的管理盘榨。ARC會在類的實例不再被使用(也就是沒有引用)時,會自動釋放其占用的內(nèi)存。

可是井佑,在少數(shù)情況下属铁,ARC需要更多地了解我們代碼之間的聯(lián)系,才能正確管理內(nèi)存躬翁。本篇文章就這少數(shù)情況而討論和分析其應用場景及如何更好地解決循環(huán)引用的問題焦蘑。

注意:ARC僅應用于類的實例。結(jié)構(gòu)體和枚舉類型是值類型盒发,不是引用類型例嘱,也不是通過引用的方式存儲和傳遞。
ARC的工作機制

創(chuàng)建類實例時宁舰,該實例就擁有了一塊內(nèi)存栗竖,存儲相關(guān)信息谓松,且會使該實例的引用計數(shù)+1碉克,如下面的person實例當前的引用計數(shù)就為1府喳,當前person對象只有一個引用倔毙。

class Person: NSObject {
  deinit {
    print("deinit")
  }
}

var person: Person? = Person()

如果有多個引用勇婴,則該實例就不會被釋放械哟,看下面的mary和lili都引用了person,使其所指向的內(nèi)存引用計數(shù)+2:

var mary = person
var lili = person

現(xiàn)在我們將mary,lili設為nil从撼,則person所指向的內(nèi)存塊引用計數(shù)-2详羡,但是并不為0仍律,因此還不會釋放。

mary = nil
lili = nil

現(xiàn)在我們再將person設為nil实柠,會如何呢水泉?自然會得到釋放了,因此就打印deinit出來了窒盐。

person = nil

為了確保使用中的實例不會被銷毀草则,ARC會跟蹤和計算每一個實例正在被多少屬性,常量和變量所引用蟹漓。哪怕實例的引用數(shù)為1炕横,ARC都不會銷毀這個實例。

無論將實例賦值給屬性葡粒、常量或變量份殿,它們都會創(chuàng)建此實例的強引用。之所以稱之為“強”引用嗽交,是因為它會將實例牢牢的保持住卿嘲,只要強引用還在,實例是不允許被銷毀的夫壁。

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

下面看一種這么一種情形:人可以有公寓拾枣,公寓可以屬于某個人。

class Person: NSObject {
   var name: String

  // 不一定人人都有公寓盒让,因此設置為可選類型更合適
  var apartment: Apartment?

  init(name: String) {
    self.name = name
  }

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

class Apartment: NSObject {
   var unit: String
  // 公寓也可能還沒有擁有者放前,設置為可選類型
  // 注意忿磅,弱引用只能聲明為變量,不能聲明為常量
  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)在將這兩個對象的指向都修改為nil:

// 現(xiàn)在釋放凭语,然后就會得到打印結(jié)果:
// deinit person: Mary
// deinit apartment: yaya
mary = nil
yaya = nil

說明這兩個對象都得到正確的釋放了葱她。這與我們預期的是一致的。

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

yaya = nil
print(mary?.apartment?.unit)

當apartment聲明為var apartment: Apartment?炒辉,其打印結(jié)果為Optional("yaya")
當apartment聲明為weak var apartment: Apartment?豪墅,其打印結(jié)果為nil。
雖然這么做兩者都得到釋放黔寇,但是這跟我們預期的結(jié)果不一致偶器。這是因為雙方都是弱引用,當yaya =nil時缝裤,其引用計數(shù)值為0屏轰,因此會被釋放。這時候再去訪問憋飞,就不存在了霎苗。如果其類型不是可選類型,釋放后再訪問榛做,會導致crash的唁盏。

如果兩個類實例之間都是可選類型,使用weak關(guān)鍵字來處理弱引用更好检眯,但應該是一強一弱引用厘擂。

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

下面的例子定義了兩個類,Customer和CreditCard锰瘸,模擬了銀行客戶和客戶的信用卡刽严。這兩個類中,每一個都將另外一個類的實例作為自身的屬性获茬。這種關(guān)系可能會造成循環(huán)強引用

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)鍵字聲明為無主引用港庄,可以聲明為變量或者常量,但是不能聲明為可選類型
  // 對于無主引用恕曲,永遠都是有值的鹏氧,因此不可聲明為可選類型。
  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é)果,我們可以看到這兩個實例都得到正確的釋放了。使用unowned關(guān)鍵字聲明為無主引用吊履,可以聲明為變量或者常量安皱,但是不能聲明為可選類型。對于無主引用艇炎,永遠都是有值的酌伊,因此不可聲明為可選類型。

注意:
如果試圖在實例被銷毀后缀踪,訪問該實例的無主引用居砖,會觸發(fā)運行時錯誤。使用無主引用驴娃,你必須確保引用始終指向一個未銷毀的實例奏候。還需要注意的是,如果試圖訪問實例已經(jīng)被銷毀的無主引用,Swift確保程序會直接崩潰唇敞,而不會發(fā)生無法預期的行為蔗草。
場景三:閉包引起的循環(huán)強引用

下面我們定義DemoView和DemoController類,前者有一個閉包作為屬性疆柔,用于反向傳值到后者咒精。

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)婆硬,強引用了self狠轻,也就是DemoController
      print("\(self.name)")
    }


    // 測試調(diào)用
    self.passToDmeoView(true)
  }

  func passToDmeoView(selected: Bool) {
    demoView?.callback(selected)
  }

  deinit {
    print("DemoController deinit")
  }
}

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

不添加[unowned self]奸例,直接使用self.name時彬犯,當返回上一個界面時,由于循環(huán)強引用查吊,DemoController對象和DemoView者得不到釋放谐区。
添加了[unowned self]后,打印結(jié)果說明可以得到釋放逻卖,如下:

DemoController deinit
demo view deinit

我們在使用閉包時宋列,一定要注意循環(huán)強引用的問題,否則很容易千萬內(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)強引用盗迟,對于weak delegate = self.delegate!也需要使用弱引用坤邪。

對于閉包內(nèi)的引用,何時使用弱引用罚缕,何時使用無主引用呢艇纺?

在閉包和捕獲的實例總是互相引用時并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為無主引用,如上面的closure閉包中的self與closure問題互相引用且同時銷毀黔衡。
在被捕獲的引用可能會變?yōu)閚il時蚓聘,將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型盟劫,并且當引用的實例被銷毀后夜牡,弱引用的值會自動置為nil。
注意:如果被捕獲的引用絕對不會變?yōu)閚il侣签,應該用無主引用氯材,而不是弱引用。如果上面的self.delegate不是可選類型硝岗,那么其值會一直存在氢哮,我們就不應該使用weak引用,而是使用unowned引用型檀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冗尤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胀溺,更是在濱河造成了極大的恐慌裂七,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仓坞,死亡現(xiàn)場離奇詭異背零,居然都是意外死亡,警方通過查閱死者的電腦和手機无埃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門徙瓶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫉称,你說我怎么就攤上這事侦镇。” “怎么了织阅?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵壳繁,是天一觀的道長。 經(jīng)常有香客問我荔棉,道長闹炉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任润樱,我火速辦了婚禮渣触,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祥国。我一直安慰自己昵观,他們只是感情好晾腔,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啊犬,像睡著了一般灼擂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上觉至,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天剔应,我揣著相機與錄音,去河邊找鬼语御。 笑死峻贮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的应闯。 我是一名探鬼主播纤控,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碉纺!你這毒婦竟也來了船万?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤骨田,失蹤者是張志新(化名)和其女友劉穎耿导,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體态贤,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡舱呻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悠汽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箱吕。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖介粘,靈堂內(nèi)的尸體忽然破棺而出殖氏,到底是詐尸還是另有隱情晚树,我是刑警寧澤姻采,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站爵憎,受9級特大地震影響慨亲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宝鼓,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一刑棵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愚铡,春花似錦蛉签、人聲如沸胡陪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柠座。三九已至,卻和暖如春片橡,著一層夾襖步出監(jiān)牢的瞬間妈经,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工捧书, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吹泡,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓经瓷,卻偏偏與公主長得像爆哑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舆吮,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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