Swift 冷門語法知識

四娘發表於2017-08-23

本來這篇文章的標題是“如何寫一個不安全的構造器”,但後面查資料的時候又發現了一些很好玩的東西,就一次性寫成一篇出來,跟大家分享一下 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 Talk #21 Structs and Mutation

最後

我想推薦一下這個視訊,主要是講 Swift 裡如何構建高效的 Collection 型別,20分鐘的長度,看完之後對於 objc.io 的那本書動心了,我基礎很差也基本上看懂了裡面的內容,講得真的很不錯,裡面平衡二叉樹的實現讓我再一次強烈地感受到 Swift 的簡潔。

覺得文章還不錯的話可以關注一下我的部落格

相關文章