Xcode8調(diào)試黑科技:Memory Graph實(shí)戰(zhàn)解決閉包引用循環(huán)問(wèn)題


Xcode8的調(diào)試技能又增加了一個(gè)黑科技:Memory Graph。簡(jiǎn)單的說(shuō)就是可以在運(yùn)行時(shí)將內(nèi)存中的對(duì)象生成一張圖。在現(xiàn)場(chǎng)的開(kāi)發(fā)者聽(tīng)到了這個(gè)消息時(shí)響起了雷鳴般的掌聲!我們來(lái)看看前方記者發(fā)回的現(xiàn)場(chǎng)照片:



媽媽說(shuō)再也不用擔(dān)心引用循環(huán)啦!除非你是個(gè)瞎子。



那么通過(guò)一個(gè)實(shí)際項(xiàng)目來(lái)練習(xí)一下吧。
首先我們寫(xiě)了一個(gè)自定義UIView:MyView。初始化的時(shí)候接收一個(gè)沒(méi)有參數(shù)也沒(méi)有返回值的閉包作為參數(shù),并存為自己的屬性:
typealias Action = () -> Void

class MyView: UIView {
    
    var action: Action?
    
    init(action: @escaping Action) {
        self.action = action
        super.init(frame: CGRect.zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

接著我們?cè)谝粋€(gè)ViewController中初始化MyView,并且也保存為屬性:

class ViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!

    var myView: MyView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myView = MyView(action: testMethod)
    }

    func testMethod() {
        label.text = "haha"
    }

}

這vc的view上有一個(gè)label控件,在viewDidLoad時(shí)初始化myView,并且將自身的一個(gè)testMethod方法當(dāng)做參數(shù)傳給了myView。
testMethod中設(shè)置了自身label的text。
注意,劃重點(diǎn)了!


這里體現(xiàn)了swift函數(shù)式的特性:函數(shù)可以自由的當(dāng)做一個(gè)變量傳遞。

這個(gè)例子影射里開(kāi)發(fā)中一個(gè)常見(jiàn)的場(chǎng)景:一個(gè)tableViewCell中有一個(gè)刪除按鈕,通過(guò)閉包將方法傳進(jìn)去,cell保存這個(gè)閉包;另一方面這個(gè)閉包被調(diào)起后,刪除某條數(shù)據(jù)后刷新數(shù)據(jù)源。

那么這么寫(xiě)會(huì)產(chǎn)生引用循環(huán)嗎?

    func testMethod() {
        label.text = "haha"
    }

核心在這段代碼上,一個(gè)類的方法里設(shè)置自身的屬性,會(huì)捕捉這個(gè)屬性嗎?這個(gè)地方可以寫(xiě)self,但是捕捉策略是unowned還是strong呢?
這個(gè)閉包的實(shí)現(xiàn)是不能自己聲明捕捉策略的:


于是就來(lái)驗(yàn)證一下。運(yùn)行起來(lái)后,push這個(gè)ViewController后pop出去(記得要進(jìn)行兩次,好像只有一次Xcode有時(shí)不會(huì)啟動(dòng)分析)。
接著點(diǎn)擊這個(gè)按鈕:



這個(gè)時(shí)候就進(jìn)入了斷點(diǎn)模式,可以查看issue面板,注意選擇右邊Runtime:



有很多嘆號(hào)說(shuō)明就有問(wèn)題了??磧?nèi)存中object的名字,有一條是Closure captures leaked。展開(kāi)后點(diǎn)擊就可以看到這個(gè)issue對(duì)應(yīng)的內(nèi)存圖形展示在中間的面板中。
當(dāng)然了,我們更多的時(shí)候是在debug頁(yè)面下查看:

注意到我們剛才的對(duì)象名:一個(gè)叫MyView,一個(gè)叫ViewController。我們pop了兩次,按理說(shuō)內(nèi)存里不應(yīng)該有這個(gè)兩個(gè)對(duì)象,然而還是有兩份實(shí)例。所以,這里面引用循環(huán)了。點(diǎn)擊紫色的嘆號(hào)會(huì)出現(xiàn)Xcode分析出來(lái)的內(nèi)存引用圖形:

有了這個(gè)圖就很容易看出來(lái)了:myView保持了action,action保持了testMethod,testMethod中因?yàn)樵O(shè)置了vc的label所以也保持了VC。所以我們可以確定:方法中隱式的self的捕捉策略是strong。這樣直接把方法傳入子view中會(huì)引起引用循環(huán)。

解決方案

1.將邏輯實(shí)現(xiàn)在一個(gè)匿名閉包里,不實(shí)現(xiàn)在類的方法上

這樣就可以自己聲明捕捉策略。這樣的方式使用就和OC的block類似了:

        myView = MyView(){ [unowned self] in
            self.label.text = "haha"
        }

2.在匿名閉包中調(diào)用方法

不是直接傳入testMethod方法,而是在傳入的閉包中調(diào)用自身的方法:

        myView = MyView(){ [unowned self] in
            self.testMethod()
        }

歡迎關(guān)注我的微博:@沒(méi)故事的卓同學(xué)

相關(guān)鏈接:
WWDC 2016 Session 410 Visual Debugging with Xcode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容