Objective-C 做爲一門資歷很老的語言,添加了 Block 這個特性後深受廣大 iOS 開發者的喜好。在 Swift 中,對應的概念叫作 Closure,即閉包。雖然更換了名字,可是概念和用法仍是類似的,就算是反作用也同樣,有可能致使循環引用。git
下面咱們用一個例子看一下,首先咱們須要第一個控制器(FirstViewController
),它所作的就是簡單的推出第二個控制器(SecondViewController
)。github
class FirstViewController: UIViewController {
private let button: UIButton = {
let button = UIButton()
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("跳轉到 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
,這是咱們用來測試循環引用問題的。還實現了 deinit
方法,用來打印一條語句,看該控制器是否被釋放了。bash
class SecondViewController: UIViewController {
private let thirdViewController = ThirdViewController()
private let button: UIButton = {
let button = UIButton()
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("跳轉到 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("調用 test 方法")
}
}
複製代碼
接下來咱們看一下 ThirdViewController
的代碼。在 ThirdViewController
中有一個按鈕,點擊一下就會觸發閉包。同時咱們還實現了 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?()
}
}
複製代碼
當咱們連續推到第三個控制器,點擊按鈕(觸發閉包)後,再回到第一個控制器,看一下三個控制器的生命週期。當流程走完後,發現控制檯只有一條語句:閉包
調用 test 方法
複製代碼
這說明閉包已經引發了循環引用問題,致使第二個控制器沒能被釋放(內存泄漏)。正是由於閉包會致使循環引用,因此在閉包中調用對象內部的方法時,都要顯式的使用 self
,提醒咱們要注意可能引發的內存泄漏問題。與 Objective-C
不一樣的是,咱們不須要在每一次使用閉包以前再繁瑣的寫上 __weak typeof(self) weakSelf = self;
了,取而代之的是捕獲列表的概念:async
@objc private func buttonClick() {
thirdViewController.closure = { [weak self] in
self?.test()
}
navigationController?.pushViewController(thirdViewController, animated: true)
}
複製代碼
再重複一次上面的流程,能夠看到控制檯多了兩條語句:ide
調用 test 方法
SecondViewController-被釋放了
ThirdViewController-被釋放了
複製代碼
只要在捕獲列表中聲明瞭你想要用弱引用的方式捕獲的對象,就能夠及時的規避由閉包致使的循環引用了。可是同時能夠看到,閉包中對於方法的調用從常規的 self.test()
變爲了可選鏈的 self?.test()
。這是由於假設閉包在子線程中執行,執行過程當中 self
在主線程隨時有可能被釋放。因爲 self
在閉包中成爲了一個弱引用,所以會自動變爲 nil
。在 Swift
中,可選類型的概念讓咱們只能以可選鏈的方式來調用 test
。下面修改一下 ThirdViewController
中的代碼:測試
@objc private func buttonClick() {
// 模擬網絡請求
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 5) {
self.closure?()
}
}
複製代碼
再次執行相同的操做步驟,此次咱們發現 test
方法沒能正確的獲得調用:優化
SecondViewController-被釋放了
ThirdViewController-被釋放了
複製代碼
在實際的項目中,這可能會致使一些問題,閉包中捕獲的 self
是 weak
的,有可能在閉包執行的過程當中就被釋放了,致使閉包中的一部分方法被執行了而一部分沒有,應用的狀態所以變得不一致。因而這個時候就要用到 Weak-Strong Dance
了。ui
既然知道了 self
在閉包中成爲了可選類型,那麼除了可選鏈,還可使用可選綁定來處理可選類型:
@objc private func buttonClick() {
thirdViewController.closure = { [weak self] in
if let strongSelf = self {
strongSelf.test()
} else {
// 處理 self 被釋放時的狀況。
}
}
navigationController?.pushViewController(thirdViewController, animated: true)
}
複製代碼
但這樣老是會讓咱們在閉包中的代碼多出兩句甚至更多,因而還有更優雅的方法,就是使用 guard
語句:
@objc private func buttonClick() {
thirdViewController.closure = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.test()
}
navigationController?.pushViewController(thirdViewController, animated: true)
}
複製代碼
一句代碼搞定~
固然,有人看到這裏會說,每次都要使用 strongSelf
來調用 self
的方法,好煩啊……那麼這一點仍是能夠進一步被優化的,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 中優雅的處理閉包致使的循環引用
若是以爲我寫的還不錯,請關注個人微博@小橘爺,最新文章即時推送~