Swift中優(yōu)雅處理閉包導(dǎo)致的循環(huán)引用詳解
前言
Objective-C 作為一門資歷很老的語言,添加了 Block 這個(gè)特性后深受廣大 iOS 開發(fā)者的喜愛。在 Swift 中,對應(yīng)的概念叫做 Closure,即閉包。雖然更換了名字,但是概念和用法還是相似的,就算是副作用也一樣,有可能導(dǎo)致循環(huán)引用。
下面我們用一個(gè)例子看一下,首先我們需要第一個(gè)控制器(FirstViewController),它所做的就是簡單的推出第二個(gè)控制器(SecondViewController)。
class FirstViewController: UIViewController { private let button: UIButton = { let button = UIButton() button.setTitleColor(UIColor.black, for: .normal) button.setTitle("跳轉(zhuǎn)到 SecondViewController", for: .normal) button.sizeToFit() return button }() override func viewDidLoad() { super.viewDidLoad() button.center = view.center view.addSubview(button) button.addTarget(self, action: #selector(buttonClick), for: .touchUpInside) } @objc private func buttonClick() { let secondViewController = SecondViewController() navigationController?.pushViewController(secondViewController, animated: true) } }
下面是 SecondViewController 的代碼。SecondViewController 所做的事情是推出第三個(gè)控制器(ThirdViewController),不同的是,thirdViewController 是作為一個(gè)屬性存在的,同時(shí)它還有一個(gè)閉包 closure ,這是我們用來測試循環(huán)引用問題的。還實(shí)現(xiàn)了 deinit 方法,用來打印一條語句,看該控制器是否被釋放了。
class SecondViewController: UIViewController { private let thirdViewController = ThirdViewController() private let button: UIButton = { let button = UIButton() button.setTitleColor(UIColor.black, for: .normal) button.setTitle("跳轉(zhuǎn)到 ThirdViewController", for: .normal) button.sizeToFit() return button }() override func viewDidLoad() { super.viewDidLoad() button.center = view.center view.addSubview(button) button.addTarget(self, action: #selector(buttonClick), for: .touchUpInside) } deinit { print("SecondViewController-被釋放了") } @objc private func buttonClick() { thirdViewController.closure = { self.test() } navigationController?.pushViewController(thirdViewController, animated: true) } private func test() { print("調(diào)用 test 方法") } }
接下來我們看一下 ThirdViewController 的代碼。在 ThirdViewController 中有一個(gè)按鈕,點(diǎn)擊一下就會(huì)觸發(fā)閉包。同時(shí)我們還實(shí)現(xiàn)了 deinit 方法,用來打印一條語句,看該控制器是否被釋放了。
class ThirdViewController: UIViewController { private let button: UIButton = { let button = UIButton() button.setTitleColor(UIColor.black, for: .normal) button.setTitle("點(diǎn)擊按鈕", for: .normal) button.sizeToFit() return button }() var closure: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() button.center = view.center view.addSubview(button) button.addTarget(self, action: #selector(buttonClick), for: .touchUpInside) } deinit { print("ThirdViewController-被釋放了") } @objc private func buttonClick() { closure?() } }
當(dāng)我們連續(xù)推到第三個(gè)控制器,點(diǎn)擊按鈕(觸發(fā)閉包)后,再回到第一個(gè)控制器,看一下三個(gè)控制器的生命周期。當(dāng)流程走完后,發(fā)現(xiàn)控制臺(tái)只有一條語句:
調(diào)用 test 方法
這說明閉包已經(jīng)引起了循環(huán)引用問題,導(dǎo)致第二個(gè)控制器沒能被釋放(內(nèi)存泄漏)。正是因?yàn)殚]包會(huì)導(dǎo)致循環(huán)引用,所以在閉包中調(diào)用對象內(nèi)部的方法時(shí),都要顯式的使用 self,提醒我們要注意可能引起的內(nèi)存泄漏問題。與 Objective-C 不同的是,我們不需要在每一次使用閉包之前再繁瑣的寫上 __weak typeof(self) weakSelf = self; 了,取而代之的是捕獲列表的概念:
@objc private func buttonClick() { thirdViewController.closure = { [weak self] in self?.test() } navigationController?.pushViewController(thirdViewController, animated: true) }
再重復(fù)一次上面的流程,可以看到控制臺(tái)多了兩條語句:
調(diào)用 test 方法
SecondViewController-被釋放了
ThirdViewController-被釋放了
只要在捕獲列表中聲明了你想要用弱引用的方式捕獲的對象,就可以及時(shí)的規(guī)避由閉包導(dǎo)致的循環(huán)引用了。但是同時(shí)可以看到,閉包中對于方法的調(diào)用從常規(guī)的 self.test() 變?yōu)榱丝蛇x鏈的 self?.test()。這是因?yàn)榧僭O(shè)閉包在子線程中執(zhí)行,執(zhí)行過程中 self 在主線程隨時(shí)有可能被釋放。由于 self 在閉包中成為了一個(gè)弱引用,因此會(huì)自動(dòng)變?yōu)?nil。在 Swift 中,可選類型的概念讓我們只能以可選鏈的方式來調(diào)用 test。下面修改一下 ThirdViewController 中的代碼:
@objc private func buttonClick() { // 模擬網(wǎng)絡(luò)請求 DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 5) { self.closure?() } }
再次執(zhí)行相同的操作步驟,這次我們發(fā)現(xiàn) test 方法沒能正確的得到調(diào)用:
SecondViewController-被釋放了
ThirdViewController-被釋放了
在實(shí)際的項(xiàng)目中,這可能會(huì)導(dǎo)致一些問題,閉包中捕獲的 self 是 weak 的,有可能在閉包執(zhí)行的過程中就被釋放了,導(dǎo)致閉包中的一部分方法被執(zhí)行了而一部分沒有,應(yīng)用的狀態(tài)因此變得不一致。于是這個(gè)時(shí)候就要用到 Weak-Strong Dance 了。
既然知道了 self 在閉包中成為了可選類型,那么除了可選鏈,還可以使用可選綁定來處理可選類型:
@objc private func buttonClick() { thirdViewController.closure = { [weak self] in if let strongSelf = self { strongSelf.test() } else { // 處理 self 被釋放時(shí)的情況。 } } navigationController?.pushViewController(thirdViewController, animated: true) }
但這樣總是會(huì)讓我們在閉包中的代碼多出兩句甚至更多,于是還有更優(yōu)雅的方法,就是使用 guard 語句:
@objc private func buttonClick() { thirdViewController.closure = { [weak self] in guard let strongSelf = self else { return } strongSelf.test() } navigationController?.pushViewController(thirdViewController, animated: true) }
一句代碼搞定~
當(dāng)然,有人看到這里會(huì)說,每次都要使用 strongSelf 來調(diào)用 self 的方法,好煩啊……那么這一點(diǎn)還是可以進(jìn)一步被優(yōu)化的,Swift 與 Objective-C 不同,是可以使用部分關(guān)鍵字來聲明變量的,于是我們可以:
@objc private func buttonClick() { thirdViewController.closure = { [weak self] in guard let `self` = self else { return } self.test() } navigationController?.pushViewController(thirdViewController, animated: true) }
這樣就可以避免每次書寫 strongSelf 的煩躁感了~
原文地址:Weak-Strong Dance In Swift——如何在 Swift 中優(yōu)雅的處理閉包導(dǎo)致的循環(huán)引用
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
Swift并發(fā)系統(tǒng)并行運(yùn)行多個(gè)任務(wù)使用詳解
這篇文章主要為大家介紹了Swift并發(fā)系統(tǒng)并行運(yùn)行多個(gè)任務(wù)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Swift設(shè)計(jì)思想Result<T>與Result<T,?E:?Error>類型解析
這篇文章主要為大家介紹了Swift設(shè)計(jì)思想Result<T>與Result<T,?E:?Error>的類型示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11理解二叉堆數(shù)據(jù)結(jié)構(gòu)及Swift的堆排序算法實(shí)現(xiàn)示例
二插堆即是完全二叉樹,對于排序可以按構(gòu)建最大堆或最小堆的方式來實(shí)現(xiàn),這里我們就來共同理解二叉堆數(shù)據(jù)結(jié)構(gòu)及Swift的堆排序算法實(shí)現(xiàn)示例2016-07-07Combine中錯(cuò)誤處理和Scheduler使用詳解
這篇文章主要為大家介紹了Combine中錯(cuò)誤處理和Scheduler使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12