Swift中優(yōu)雅處理閉包導致的循環(huán)引用詳解
前言
Objective-C 作為一門資歷很老的語言,添加了 Block 這個特性后深受廣大 iOS 開發(fā)者的喜愛。在 Swift 中,對應的概念叫做 Closure,即閉包。雖然更換了名字,但是概念和用法還是相似的,就算是副作用也一樣,有可能導致循環(huán)引用。
下面我們用一個例子看一下,首先我們需要第一個控制器(FirstViewController),它所做的就是簡單的推出第二個控制器(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 所做的事情是推出第三個控制器(ThirdViewController),不同的是,thirdViewController 是作為一個屬性存在的,同時它還有一個閉包 closure ,這是我們用來測試循環(huán)引用問題的。還實現(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 中有一個按鈕,點擊一下就會觸發(fā)閉包。同時我們還實現(xiàn)了 deinit 方法,用來打印一條語句,看該控制器是否被釋放了。
class ThirdViewController: UIViewController {
private let button: UIButton = {
let button = UIButton()
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("點擊按鈕", 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?()
}
}
當我們連續(xù)推到第三個控制器,點擊按鈕(觸發(fā)閉包)后,再回到第一個控制器,看一下三個控制器的生命周期。當流程走完后,發(fā)現(xiàn)控制臺只有一條語句:
調(diào)用 test 方法
這說明閉包已經(jīng)引起了循環(huán)引用問題,導致第二個控制器沒能被釋放(內(nèi)存泄漏)。正是因為閉包會導致循環(huán)引用,所以在閉包中調(diào)用對象內(nèi)部的方法時,都要顯式的使用 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)
}
再重復一次上面的流程,可以看到控制臺多了兩條語句:
調(diào)用 test 方法
SecondViewController-被釋放了
ThirdViewController-被釋放了
只要在捕獲列表中聲明了你想要用弱引用的方式捕獲的對象,就可以及時的規(guī)避由閉包導致的循環(huán)引用了。但是同時可以看到,閉包中對于方法的調(diào)用從常規(guī)的 self.test() 變?yōu)榱丝蛇x鏈的 self?.test()。這是因為假設閉包在子線程中執(zhí)行,執(zhí)行過程中 self 在主線程隨時有可能被釋放。由于 self 在閉包中成為了一個弱引用,因此會自動變?yōu)?nil。在 Swift 中,可選類型的概念讓我們只能以可選鏈的方式來調(diào)用 test。下面修改一下 ThirdViewController 中的代碼:
@objc private func buttonClick() {
// 模擬網(wǎng)絡請求
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 5) {
self.closure?()
}
}
再次執(zhí)行相同的操作步驟,這次我們發(fā)現(xiàn) test 方法沒能正確的得到調(diào)用:
SecondViewController-被釋放了
ThirdViewController-被釋放了
在實際的項目中,這可能會導致一些問題,閉包中捕獲的 self 是 weak 的,有可能在閉包執(zhí)行的過程中就被釋放了,導致閉包中的一部分方法被執(zhí)行了而一部分沒有,應用的狀態(tài)因此變得不一致。于是這個時候就要用到 Weak-Strong Dance 了。
既然知道了 self 在閉包中成為了可選類型,那么除了可選鏈,還可以使用可選綁定來處理可選類型:
@objc private func buttonClick() {
thirdViewController.closure = { [weak self] in
if let strongSelf = self {
strongSelf.test()
} else {
// 處理 self 被釋放時的情況。
}
}
navigationController?.pushViewController(thirdViewController, animated: true)
}
但這樣總是會讓我們在閉包中的代碼多出兩句甚至更多,于是還有更優(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)
}
一句代碼搞定~
當然,有人看到這里會說,每次都要使用 strongSelf 來調(diào)用 self 的方法,好煩啊……那么這一點還是可以進一步被優(yōu)化的,Swift 與 Objective-C 不同,是可以使用部分關鍵字來聲明變量的,于是我們可以:
@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)雅的處理閉包導致的循環(huán)引用
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支持。
上一篇:Swift使用CollectionView實現(xiàn)廣告欄滑動效果
欄 目:Swift
本文標題:Swift中優(yōu)雅處理閉包導致的循環(huán)引用詳解
本文地址:http://www.jygsgssxh.com/a1/Swift/11925.html
您可能感興趣的文章
- 01-11swift中defer幾個簡單的使用場景詳解
- 01-11Swift利用Decodable解析JSON的一個小問題詳解
- 01-11Swift中defer關鍵字推遲執(zhí)行示例詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中定義單例的方法實例
- 01-11Swift利用純代碼實現(xiàn)時鐘效果實例代碼
- 01-11Swift中排序算法的簡單取舍詳解
- 01-11Swift如何為設置中心添加常用功能
- 01-11Swift Json實例詳細解析
- 01-11Swift利用指紋識別或面部識別為應用添加私密保護功能


閱讀排行
本欄相關
- 01-11Swift利用Decodable解析JSON的一個小問題
- 01-11swift中defer幾個簡單的使用場景詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中defer關鍵字推遲執(zhí)行示例詳解
- 01-11Swift利用純代碼實現(xiàn)時鐘效果實例代碼
- 01-11Swift中定義單例的方法實例
- 01-11Swift中排序算法的簡單取舍詳解
- 01-11Swift Json實例詳細解析
- 01-11Swift如何為設置中心添加常用功能
- 01-11Swift利用指紋識別或面部識別為應用添
隨機閱讀
- 01-10delphi制作wav文件的方法
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 04-02jquery與jsp,用jquery
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10C#中split用法實例總結(jié)
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-11ajax實現(xiàn)頁面的局部加載


