本來這篇文章的標題是“如何寫一個不安全的構造器”,但後面查資料的時候又發現了一些很好玩的東西,就一次性寫成一篇出來,跟大家分享一下 Swift 裡的幾個 best pratice:
- 帶關聯值的 Enum 的構造器
- strongSelf 的另一種寫法
- 如何在 Swift 裡寫一個不安全的構造器
- mutating 函式的定義
帶關聯值的 Enum 的構造器
寫 Swift 的人應該很熟悉帶關聯值的列舉(Enumeration with Associated Value),例如原生的 Optional,錯誤處理的 Result 庫等等,但在我嘗試自定義列舉的構造器時遇到了這樣的問題:
enum CustomOptional<Wrapped> {
case value(Wrapped)
case none
init(value: Wrapped) {
return .value(value)
// error: 'nil' is the only return value /
// permitted in an initializer
}
}複製程式碼
錯誤提示是構造器裡只能夠返回 nil,但如此一來我們就好像沒有辦法把構造器實現出來了。我想起在使用 Result 的時候有用到過它的構造器,查閱之後,發現正確的做法應該是這樣的:
enum CustomOptional<Wrapped> {
case value(Wrapped)
case none
init(value: Wrapped) {
self = .value(value)
}
}複製程式碼
順帶說一句,所有的值型別都支援這種寫法。
出處:Result — Swift type modelling the success/failure of arbitrary operations
strongSelf 的另一種寫法
之前我就寫過一篇文章來講這個,之所以再提一次,一方面是為了文章的完整性,另一方面就是為了下文的另一個語法做鋪墊。
從 OC 帶過來的命名方式,會讓我們在閉包裡這麼去寫 strongSelf:
block = { [weak self] in
guard let strongSelf = self else { return }
... other code ...
}複製程式碼
strongSelf
在程式碼裡的出現其實會有點突兀,我會更喜歡利用 Swift 一種語法,讓程式碼變得統一:
block = { [weak self] in
guard let `self` = self else { return }
... other code ...
}複製程式碼
這裡宣告瞭一個區域性變數 self
,讓我們可以直接用來將捕獲的 weak self 解包出來,由於 self
是系統關鍵字,使用 ` 包住關鍵字,可以讓編譯器把它看做是一個正常的變數名稱。
然後我們在閉包裡使用 self
時,就不必考慮它是否會產生迴圈引用的問題,別的地方的程式碼也可以很方便地複製貼上過來,不用把 self
全部都改為 strongSelf
。
出處:忘了?
Update 2017.08.24:
感謝大神在評論裡提醒我,原來這是一個編譯的 bug,提案 SE-0079 很詳細地講了這件事情,但目前這個 bug 還沒有修復,按照上面的方法去寫就可以了。
如果這個 bug 被修復了的話,就可以沒必要加上 `,可以直接宣告區域性變數 self:
block = { [weak self] in
guard let self = self else { return }
... other code ...
}複製程式碼
如何在 Swift 裡寫一個不安全的構造器
開頭我提到了這篇文章原本的標題是叫做“如何寫一個不安全的構造器”,其實我是在寫這篇文章的時候才發現了上面的語法,之前我是用了另外一種比較 dirty 的方式去做的:
enum CustomOptional<Wrapped> {
case value(Wrapped)
case none
static func `init`<Wrapped>(value: Wrapped) -> CustomOptional<Wrapped> {
return .value(value)
}
}複製程式碼
很早的時候我就嘗試過定義一個名為 init
的 static 函式,得到的是這樣的提示 error: keyword 'init' cannot be used as an identifier here
,也就是說 init 作為系統關鍵字不能在這裡使用,那麼很簡單,用 ` 把它包住就行了。
這麼定義 init
方法的話,在呼叫時也可以像正常的構造器那樣省略掉 init
:
let _ = CustomOptional(value: "I'm a String")複製程式碼
這種“構造器”的定義和實現都很靈活,可以返回任何型別,內部實現也不需要遵守那麼多規則。這可能在一些我意想不到的場景下會有用吧,但我暫時沒有想到,如果你恰好用到了這個小技巧,請務必發個郵件告訴我,我很好奇具體的使用場景。
出處:kemchenj
mutating 函式的定義
定義值型別的時候,同一個函式,我們經常需要定義 mutating 和 non-mutating 兩個版本:
func sorted() -> Array { ... }
mutating func sort() { ... }複製程式碼
但絕大部分情況下這兩個函式的實現基本上都是一樣的,這個時候我們就可以考慮複用其中一個,減少重複程式碼:
func sorted() -> Array { ... }
mutating func sort() {
self = sorted()
}複製程式碼
之所以可以這樣寫,是因為 mutating 意味著函式會對值自身進行修改:
self.property = value
// 等價於
var newStruct = self
newStruct.property = value
self = newStruct複製程式碼
最後
我想推薦一下這個視訊,主要是講 Swift 裡如何構建高效的 Collection 型別,20分鐘的長度,看完之後對於 objc.io 的那本書動心了,我基礎很差也基本上看懂了裡面的內容,講得真的很不錯,裡面平衡二叉樹的實現讓我再一次強烈地感受到 Swift 的簡潔。
覺得文章還不錯的話可以關注一下我的部落格