
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