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

