雷火电竞-中国电竞赛事及体育赛事平台

歡迎來到入門教程網(wǎng)!

Swift

當前位置:主頁 > 軟件編程 > Swift >

Swift中優(yōu)雅處理閉包導致的循環(huán)引用詳解

來源:本站原創(chuàng)|時間:2020-01-11|欄目:Swift|點擊:

前言

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如何使用類型擦除及自定義詳解

本文標題:Swift中優(yōu)雅處理閉包導致的循環(huán)引用詳解

本文地址:http://www.jygsgssxh.com/a1/Swift/11925.html

網(wǎng)頁制作CMS教程網(wǎng)絡編程軟件編程腳本語言數(shù)據(jù)庫服務器

如果侵犯了您的權(quán)利,請與我們聯(lián)系,我們將在24小時內(nèi)進行處理、任何非本站因素導致的法律后果,本站均不負任何責任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有