- 原文地址:Swift Lazy Initialization with Closures
- 原文作者:Bob Lee
- 譯文出自:掘金翻譯計劃
- 譯者:lsvih
- 校對者:zhangqippp,Zheaoli
在 Swift 中使用閉包實現懶載入
學習如何兼顧模組化與可讀性來建立物件
親愛的讀者你們好!我是 Bob,很高興能在這篇文章中與你們相遇!如你想加入我的郵件列表,獲取更多學習 iOS 開發的文章,請點選這兒註冊,很快就能完成的哦 :)
動機
在我剛開始學習 iOS 開發的時候,我在 YouTube 上找了一些教程。我發現這些教程有時候會用下面這種方式來建立 UI 物件:
let makeBox: UIView = {
let view = UIView()
return view
}()複製程式碼
作為一個初學者,我自然而然地複製並使用了這個例子。直到有一天,我的一個讀者問我:“為什麼你要加上{}
呢?最後為什麼要加上一對()
呢?這是一個計算屬性嗎?”我啞口無言,因為我自己也不知道答案。
因此,我為過去年輕的自己寫下了這份教程。說不定還能幫上其他人的忙。
目標
這篇教程有一下三個目標:第一,瞭解如何像前面的程式碼一樣,非常規地建立物件;第二,知道編在寫 Swfit 程式碼時,什麼時候該使用 lazy var
;第三,快加入我的郵件列表呀。
預備知識
為了讓你能輕鬆愉快地和我一起完成這篇教程,我強烈推薦你先了解下面這幾個概念。
- 閉包
- 捕獲列表與迴圈引用 [weak self]
- 物件導向程式設計
建立 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複製程式碼
為了方便,你可以:
使用快捷鍵: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
例項。
然而,這樣做你建立了兩個常量:createBob
與 babyBob
。如何把所有的東西都放在一個宣告中呢?請看:
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
的一些規定
- 你不能把
lazy
和let
一起用,因為用lazy
時沒有初值,只有當被訪問時才會獲得值。 - 你不能把它和
計算屬性
一起用,因為在你修改任何與lazy
的計算屬性有關的變數時,計算屬性都會被重新計算(耗費 CPU 資源)。 Lazy
只能是結構或類的成員。
Lazy 能被捕獲嗎?
如果你讀過我的前一篇文章《Swift 閉包和代理中的迴圈引用》,你就會明白這個問題。讓我們試一試吧。建立一個名叫 BobGreet
的類,它有兩個屬性:一個是型別為 String
的 name
,一個是型別為 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)。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃。