使用 Proxy 解決對(duì)象之間循環(huán)引用

循環(huán)引用(Circular Reference)是指兩個(gè)對(duì)象之間相互強(qiáng)引用螃诅,兩者無法按時(shí)釋放,從而導(dǎo)致內(nèi)存泄漏,是 iOS/macOS 開發(fā)人員經(jīng)常遇見的一種內(nèi)存管理問題揪胃。

這種問題的解決方式一般有兩種,一是對(duì)其中一個(gè)對(duì)象設(shè)置為弱引用氛琢,二是在其中一個(gè)對(duì)象需要釋放時(shí)喊递,強(qiáng)制將另一個(gè)對(duì)象置空,兩種方式的原理都是打破持有引用閉環(huán)阳似。

通常來講骚勘,這些操作不會(huì)出現(xiàn)什么問題,然而一些個(gè)別情況需要我們額外注意:兩個(gè)對(duì)象之間都必須強(qiáng)引用撮奏,并且需要在一個(gè)對(duì)象 delloc 時(shí)才能釋放另一個(gè)對(duì)象调鲸。

這里要特別指出一點(diǎn)盛杰,兩個(gè)對(duì)象必須互相強(qiáng)引用的情況會(huì)很少,大多數(shù)有經(jīng)驗(yàn)的開發(fā)者都會(huì)刻意避免這個(gè)情況發(fā)生藐石。但這并不是必須的即供,比如我們使用 NSTime 時(shí),或者某個(gè) view 必須強(qiáng)引用它的持有者時(shí)于微。

這其中 NSTimer 是個(gè)典型的例子逗嫡,我相信大多數(shù)人都遇見過使用 NSTimer 導(dǎo)致其持用對(duì)象無法釋放的問題,然后不得已在某個(gè)時(shí)機(jī)提前將 timer 置空釋放株依。我不太推薦這樣的做法驱证,因?yàn)殇N毀 timer 的邏輯可能分散在文件各處,難以維護(hù)恋腕,而且我們也可能忘記在特定事件中手動(dòng)銷毀 timer抹锄。另外假如這個(gè) timer 只能在持有者 dealloc/deinit 時(shí)釋放,你的處境會(huì)很尷尬:dealloc/deinit 不會(huì)被執(zhí)行荠藤。

timer = Timer.scheduledTimer(timeInterval: 1,
                             target: self,
                             selector: #selector(handleTimer),
                             userInfo: nil,
                             repeats: true)
RunLoop.current.add(timer, forMode: .common)

@objc func handleTimer() {
}

deinit {
    timer.invalidate()  // Not work.
}

這里我會(huì)介紹一種更友好的方式處理這個(gè)問題:代理模式伙单。

假設(shè) A 對(duì)象持有 B 對(duì)象,當(dāng) B 對(duì)象需要持有 A 對(duì)象時(shí)哈肖,我們退一步轉(zhuǎn)而讓其持有 C 對(duì)象(代理對(duì)象)吻育,C 對(duì)象中包含一個(gè)弱指針指向 A 對(duì)象地址,B 對(duì)象要給 A 對(duì)象發(fā)送的所有消息均由代理對(duì)象 C 轉(zhuǎn)發(fā)給 A淤井,這樣 A 與 B 之間的引用閉環(huán)會(huì)被打破布疼。這三者之間的關(guān)系如下:

      A <-  -  -  -  -  +
      +                 |
      |
      |                 |  弱引用指針
      |
      |                 |
      | 持有
      |                 |
      |
      v                 |
      B+---------------->C
             持有

在 Foundation 框架中有一個(gè)神奇的類 NSProxy 最適合扮演代理的角色。這是一個(gè)抽象類币狠,用來作為目標(biāo)對(duì)象的替身游两。只要實(shí)現(xiàn)了它的兩個(gè)方法,它本身接收到的所有消息均被轉(zhuǎn)發(fā)至目標(biāo)對(duì)象漩绵,太完美了器罐,遺憾的是 Swift 并不能使用,好在我們還可以使用 NSObject 的消息轉(zhuǎn)發(fā)機(jī)制去實(shí)現(xiàn)渐行。

現(xiàn)在轰坊,我們可以自己實(shí)現(xiàn)一個(gè) Proxy,取類名為 WeakProxy:

class WeakProxy: NSObject {
    
    private(set) weak var target: AnyObject?
    
    private override init() { super.init() }

    convenience init(_ target: AnyObject?) {
        self.init()
        self.target = target
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return target
    }
}

上面的代碼中使用了一個(gè)小技巧來隱藏父類的構(gòu)造器祟印,我們只需使用其便利構(gòu)造器肴沫。

最后,只需要這樣一個(gè)小小的修改就可以解決 timer 的持有者無法釋放的問題蕴忆,我們將初始化 timer 時(shí)的 target 指定為這個(gè)代理對(duì)象颤芬,然后我們就可以在 dealloc/deinit 中釋放 timer 了。

timer = Timer.scheduledTimer(timeInterval: 1,
                             target: WeakProxy(self),
                             selector: #selector(handleTimer),
                             userInfo: nil,
                             repeats: true)
RunLoop.current.add(timer, forMode: .common)

@objc func handleTimer() {
}

deinit {
    timer.invalidate()  // Work well.
}

WeakProxy 并不僅僅局限于 timer 的使用場(chǎng)景,剩下的各位讀者可以自行發(fā)掘站蝠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汰具,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菱魔,更是在濱河造成了極大的恐慌留荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜倦,死亡現(xiàn)場(chǎng)離奇詭異聚蝶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)藻治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門碘勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桩卵,你說我怎么就攤上這事验靡。” “怎么了雏节?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵胜嗓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我矾屯,道長(zhǎng),這世上最難降的妖魔是什么初厚? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任件蚕,我火速辦了婚禮,結(jié)果婚禮上产禾,老公的妹妹穿的比我還像新娘排作。我一直安慰自己,他們只是感情好亚情,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布妄痪。 她就那樣靜靜地躺著,像睡著了一般楞件。 火紅的嫁衣襯著肌膚如雪衫生。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天土浸,我揣著相機(jī)與錄音罪针,去河邊找鬼。 笑死黄伊,一個(gè)胖子當(dāng)著我的面吹牛泪酱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼墓阀,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼毡惜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斯撮,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤经伙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吮成,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體橱乱,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年粱甫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泳叠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茶宵,死狀恐怖危纫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乌庶,我是刑警寧澤种蝶,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站瞒大,受9級(jí)特大地震影響螃征,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜透敌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一盯滚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酗电,春花似錦魄藕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嫩与,卻和暖如春寝姿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背划滋。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工会油, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人古毛。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓翻翩,卻偏偏與公主長(zhǎng)得像都许,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫂冻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,111評(píng)論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,416評(píng)論 8 265
  • # 前言 反復(fù)地復(fù)習(xí)iOS基礎(chǔ)知識(shí)和原理胶征,打磨知識(shí)體系是非常重要的,本篇就是重新溫習(xí)iOS的內(nèi)存管理桨仿。 內(nèi)存管理是...
    Vein_閱讀 801評(píng)論 0 2
  • 《極限挑戰(zhàn)》第四季第一期的主題是知識(shí)改變命運(yùn)服傍,第二場(chǎng)在崇明中學(xué)的六個(gè)問題令我震撼不已钱雷。 首先,附上這六個(gè)問題: 1...
    梁景辰閱讀 525評(píng)論 0 3
  • 最近有很多關(guān)于吉芬商品的問題吹零,討論多的是關(guān)于股票到底是不是吉芬商品罩抗。 我們先來回顧一下吉芬商品的定義:是指某種生活...
    果大喵喵閱讀 3,384評(píng)論 0 0