在 Swift 中使用閉包實現懶加載

在 Swift 中使用閉包實現懶加載

學習如何兼顧模塊化與可讀性來建立對象

圖爲蘋果的 Magic Keyboard 2 與 Magic Mouse 2

親愛的讀者大家好!我是 Bob,很高興能在這篇文章中與大家相遇!如你想加入個人郵件列表,獲取更多學習 iOS 開發的文章,請點擊這兒註冊,很快就能完成的哦 :)javascript

動機

在我剛開始學習 iOS 開發的時候,我在 YouTube 上找了一些教程。我發現這些教程有時候會用下面這種方式來建立 UI 對象:前端

let makeBox: UIView = {
 let view = UIView()
 return view
}()複製代碼

做爲一個初學者,我天然而然地複製並使用了這個例子。直到有一天,個人一個讀者問我:「爲何你要加上{}呢?最後爲何要加上一對()呢?這是一個計算屬性嗎?」我啞口無言,由於我本身也不知道答案。java

所以,我爲過去年輕的本身寫下了這份教程。說不定還能幫上其餘人的忙。react

目標

這篇教程有一下三個目標:第一,瞭解如何像前面的代碼同樣,很是規地建立對象;第二,知道編在寫 Swfit 代碼時,何時該使用 lazy var;第三,快加入個人郵件列表呀。android

預備知識

爲了讓你能輕鬆愉快地和我一塊兒完成這篇教程,我強烈推薦你先了解下面這幾個概念。ios

  1. 閉包
  2. 捕獲列表與循環引用 [weak self]
  3. 面向對象程序設計

建立 UI 組件

在我介紹「很是規」方法以前,讓咱們先複習一下「常規」方法。在 Swift 中,若是你要建立一個按鈕,你應該會這麼作:git

// 設定尺寸
let buttonSize = CGRect(x: 0, y: 0, width: 100, height: 100)

// 建立控件
let bobButton = UIButton(frame: buttonSize)
bobButton.backgroundColor = .black
bobButton.titleLabel?.text = "Bob"
bobButton.titleLabel?.textColor = .white複製代碼

這樣作沒問題github

假設如今你要建立另外三個按鈕,你極可能會把上面的代碼複製,而後把變量名從 bobButton 改爲 bobbyButton數據庫

這未免也太枯燥了吧。編程

// New Button 
let bobbyButton = UIButton(frame: buttonSize)
bobbyButton.backgroundColor = .black
bobbyButton.titleLabel?.text = "Bob"
bobbyButton.titleLabel?.textColor = .white複製代碼

爲了方便,你能夠:

使用快捷鍵:ctrl-cmd-e 來完成這個工做。

若是你不想作重複的工做,你也能夠建立一個函數。

func createButton(enterTitle: String) -> UIButton {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = enterTitle
 return button
}
createButton(enterTitle: "Yoyo") // 👍複製代碼

然而,在 iOS 開發中,不多會看到一堆如出一轍的按鈕。所以,這個函數須要接受更多的參數,如背景顏色、文字、圓角尺寸、陰影等等。你的函數最後可能會變成這樣:

func createButton(title: String, borderWidth: Double, backgrounColor, ...) -> Button複製代碼

可是,即便你爲這個函數加上了默認參數,上面的代碼依然不理想。這樣的設計下降了代碼的可讀性。所以,比起這個方法,咱們仍是採用上面那個」單調「的方法爲妙。

到底有沒有辦法讓咱們既不那麼枯燥,還能讓代碼更有條理呢?固然咯。咱們如今只是複習你過去的作法——是時候更上一層樓,展望你將來的作法了。

介紹」很是規「方法

在咱們使用」很是規「方法建立 UI 組件以前,讓咱們先回答一下最開始那個讀者的問題。{}是什麼意思,它是一個計算屬性嗎?

固然不是,它只是一個閉包

首先,讓我來示範一下如何用閉包來建立一個對象。咱們設計一個名爲Human的結構:

struct Human {
 init() {
  print("Born 1996")
 }
}複製代碼

如今,讓你看看怎麼用閉包建立對象:

let createBob = { () -> Human in
 let human = Human()
 return human
}

let babyBob = createBob() // "Born 1996"複製代碼

若是你不熟悉這段語法,請先中止閱讀這篇文章,去看看 Fear No Closure with Bob 充充電吧。

解釋一下,createBob 是一個類型爲 ()-> Human 的閉包。你已經經過調用 createBob() 建立好了一個 babyBob 實例。

然而,這樣作你建立了兩個常量:createBobbabyBob。如何把全部的東西都放在一個聲明中呢?請看:

let bobby = { () -> Human in
 let human = Human()
 return human
}()複製代碼

如今,這個閉包經過在最後加上 () 執行了本身,bobby 如今被賦值爲一個 Human 對象。乾的漂亮!

如今你已經學會了使用閉包來建立一個對象

讓咱們應用這個方法,模仿上面的例子來建立一個 UI 對象吧。

let bobView = { () -> UIView in
 let view = UIView()
 view.backgroundColor = .black
 return view
}()複製代碼

很好,咱們還能讓它更簡潔。實際上,咱們不須要爲閉包指定類型,咱們只須要指定 bobView 實例的類型就夠了。例如:

let bobbyView: **UIView** = {
 let view = UIView()
 view.backgroundColor = .black
 return view
}()複製代碼

Swift 可以經過關鍵字 return 推導出這個閉包的類型是 () -> UIView

如今看看,上面的例子已經和我以前懼怕的「很是規方式」同樣了。

使用閉包建立的好處

咱們已經討論了直接建立對象的單調和使用構造函數帶來的問題。如今你可能會想「爲何我非得用閉包來建立?」

重複起來更容易

我不喜歡用 Storyboard,我比較喜歡複製粘貼用代碼來建立 UI 對象。實際上,在個人電腦裏有一個「代碼庫」。假設庫裏有個按鈕,代碼以下:

let myButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 
 button.layer.masksToBounds = true
return button
}()複製代碼

我只須要把它整個複製,而後把名字從 myButton 改爲 newButtom 就好了。在我用閉包以前,我得重複地把 myButton 改爲 newButtom ,甚至要改上七八遍。咱們雖然能夠用 Xcode 的快捷鍵,但爲啥不使用閉包,讓這件事更簡單呢?

看起來更簡潔

因爲對象對象會本身編好組,在我看來它更加的簡潔。讓咱們對比一下:

// 使用閉包建立 
let leftCornerButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 
 button.layer.masksToBounds = true
return button
}()

let rightCornerButton: UIButton = {
 let button = UIButton(frame: buttonSize)
 button.backgroundColor = .black
 button.titleLabel?.text = "Button"
 button.titleLabel?.textColor = .white
 button.layer.cornerRadius = 
 button.layer.masksToBounds = true
return button
}()複製代碼

vs

// 手動建立
let leftCornerButton = UIButton(frame: buttonSize)
leftCornerButton.backgroundColor = .black
leftCornerButton.titleLabel?.text = "Button"
leftCornerButton.titleLabel?.textColor = .white
leftCornerButton.layer.cornerRadius = 
leftCornerButton.layer.masksToBounds = true

let rightCornerButton = UIButton(frame: buttonSize)
rightCornerButton.backgroundColor = .black
rightCornerButton.titleLabel?.text = "Button"
rightCornerButton.titleLabel?.textColor = .white
rightCornerButton.layer.cornerRadius = 
rightCornerButton.layer.masksToBounds = true複製代碼

儘管使用閉包建立對象要多出幾行,可是比起要在 rightCornerButton 或者 leftCornerButton 後面狂加屬性,我仍是更喜歡在 button 後面加屬性。

實際上若是按鈕的命名特別詳細時,用閉包建立對象還能夠少幾行。

恭喜你,你已經完成了咱們的第一個目標

懶加載的應用

辛苦了!如今讓咱們來看看這個教程的第二個目標吧。

你可能看過與下面相似的代碼:

class IntenseMathProblem {
 lazy var complexNumber: Int = {
  // 請想象這兒要耗費不少CPU資源
  1 * 1
 }()
}複製代碼

lazy 的做用是,讓 complexNumber 屬性只有在你試圖訪問它的時候纔會被計算。例如:

let problem = IntenseMathProblem 
problem()  // 此時complexNumber沒有值複製代碼

沒錯,如今 complexNumber 沒有值。然而,一旦你訪問這個屬性:

problem().complexNumber // 如今回返回1複製代碼

lazy var 常常用於數據庫排序或者從後端取數據,由於你並不想在建立對象的時候就把全部東西都計算、排序。

實際上,因爲對象太大了致使 RAM 撐不住,你的手機就會崩潰。

應用

如下是 lazy var 的應用:

排序

class SortManager {
 lazy var sortNumberFromDatabase: [Int] = {
  // 排序邏輯
  return [1, 2, 3, 4]
 }()
}複製代碼

圖片壓縮

class CompressionManager {
 lazy var compressedImage: UIImage = {
  let image = UIImage()
  // 壓縮圖片的
  // 邏輯
  return image
 }()
}複製代碼

Lazy的一些規定

  1. 你不能把 lazylet 一塊兒用,由於用 lazy 時沒有初值,只有當被訪問時纔會得到值。
  2. 你不能把它和 計算屬性 一塊兒用,由於在你修改任何與 lazy 的計算屬性有關的變量時,計算屬性都會被從新計算(耗費 CPU 資源)。
  3. Lazy 只能是結構或類的成員。

Lazy 能被捕獲嗎?

若是你讀過個人前一篇文章《Swift 閉包和代理中的循環引用》,你就會明白這個問題。讓咱們試一試吧。建立一個名叫 BobGreet 的類,它有兩個屬性:一個是類型爲 Stringname,一個是類型爲 String 可是使用閉包建立的 greeting

class BobGreet {
 var name = "Bob the Developer"
 lazy var greeting: String = {
  return "Hello, \(self.name)"
 }()

deinit { 
  print("I'm gone, bruh 🙆")}
 }
}複製代碼

閉包可能BobGuest 有強引用,讓咱們嘗試着 deallocate 它。

var bobGreet: BobGreet? = BobClass()
bobGreet?.greeting
bobClass = nil // I'm gone, bruh 🙆複製代碼

不用擔憂 [unowned self],閉包並無對對象存在引用。相反,它僅僅是在閉包內複製了 self。若是你對前面的代碼聲明有疑問,能夠讀讀 Swift Capture Lists 來了解更多這方面的知識。👍

最後的嘮叨

我在準備這篇教程的過程當中也學到了不少,但願你也同樣。感謝大家的熱情❤️!不過這篇文章還剩一點:個人最後一個目標。若是你但願加入個人郵件列表以得到更多有價值的信息的話,你能夠點 這裏註冊。

正如封面照片所示,我最近買了 Magic Keyboard 和 Magic Mouse。它們超級棒,幫我提高了不少的效率。你能夠在 這兒買鼠標,在 這兒買鍵盤。我纔不會由於它們的價格心疼呢。😓

本文的源碼

我將要參加 Swift 討論會

我將在 6 月 1 日至 6 月 2 日 參加我有生以來的第一次討論會 @SwiftAveir, 個人朋友 Joao協助組織了此次會議,因此我很是 excited。你能夠點這兒瞭解這件事 的詳情!

文章推薦

函數式編程簡介 (Blog)

我最愛的 XCode 快捷鍵 (Blog )

關於我

我是一名來自首爾的 iOS 課程教師,你能夠在 Instagram 上了解我。我會常常在 Facebook Page 投稿,投稿時間通常在北京時間上午9點(Sat 8pm EST)。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索