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

lsvih發表於2017-05-04

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

學習如何兼顧模組化與可讀性來建立物件

在 Swift 中使用閉包實現懶載入
圖為蘋果的 Magic Keyboard 2 與 Magic Mouse 2

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

動機

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

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

作為一個初學者,我自然而然地複製並使用了這個例子。直到有一天,我的一個讀者問我:“為什麼你要加上{}呢?最後為什麼要加上一對()呢?這是一個計算屬性嗎?”我啞口無言,因為我自己也不知道答案。

因此,我為過去年輕的自己寫下了這份教程。說不定還能幫上其他人的忙。

目標

這篇教程有一下三個目標:第一,瞭解如何像前面的程式碼一樣,非常規地建立物件;第二,知道編在寫 Swfit 程式碼時,什麼時候該使用 lazy var;第三,快加入我的郵件列表呀。

預備知識

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

  1. 閉包
  2. 捕獲列表與迴圈引用 [weak self]
  3. 物件導向程式設計

建立 UI 元件

在我介紹“非常規”方法之前,讓我們先複習一下“常規”方法。在 Swift 中,如果你要建立一個按鈕,你應該會這麼做:

// 設定尺寸
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複製程式碼

這樣做沒問題

假設現在你要建立另外三個按鈕,你很可能會把上面的程式碼複製,然後把變數名從 bobButton 改成 bobbyButton

這未免也太枯燥了吧。

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

為了方便,你可以:

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

使用快捷鍵: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前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章