今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

使用@autoclosure來設計Swift API

使用 @autoclosure 來設計 Swift API#

此文是翻譯#

原文連結:Using @autoclosure when designing Swift APIs

Swift @autoclosure 屬性用於在閉包中定義一個 “被包裹” 的參數。主要用於延遲執行一段(潛在的耗時、占資源大)代碼到真正需要的時候,而不是在參數傳遞時就執行。

在 Swift 標準庫中就有一個例子,Assert 函數的使用。由於 asserts 僅僅在 debug 模式下觸發,所以沒必要在 release 模式下執行代碼。這時就用到了 @autoclosure

    func assert(_ expression: @autoclosure() -> Bool,
                _ message: @autoclosure() -> String) {
        guard isDebug else {
            return
        }

        // Inside assert we can refer to expression as a normal closure
        if !expression() {
            assertionFailure(message())
        }
    }

上面是我理解的 assert 的實現,assert 的真正實現在這裡

@autoclosure 的一個好處是對於調用位置沒有影響。如果 assert 是用 “一般” 閉包的方式實現的,那它的使用是這樣:

assert({ someConfition() }, { "Hey, it failed!" })

但是現在,它可以被當作函數接收無閉包參數來使用:

assert(someCondition(), "Hey it failed!")

這週,我們來看一下怎麼在我們自己的代碼中使用 @autoclosure,以及如何使用它設計出優雅的 API。

內聯函數#

@autoclosure 的一個作用是在函數調用中內聯表達式。這使得我們可以把指定的表達式做為一個參數來使用。我們來看一下下面的例子:

在 iOS 中,通常使用下面的 API 來實現 view 動畫:

UIView.animate(withDuration: 0.25) {
    view.frame.origin.y = 100
}

使用 @autoclosure,我們可以寫一個動畫函數,自己創建動畫的閉包,並且執行。像下面這樣:

func animate(_ animation: @autoclosure @escaping () -> Void,
            duration: TimeInterval = 0.25) {
    UIView.animate(withDuration: duration, animations: animation)
}

然後,我們就可以僅僅調用一個簡單的函數,沒有多餘的 {},來調用動畫

animate(view.frame.origin.y = 100)

通過上面的方法,我們減少了冗長的動畫代碼,且沒有丟失可讀性。

將錯誤作為表達式傳遞#

@autoclosure 的另一個十分有用的地方是,在寫處理錯誤的工具類的時。例如,我們想要通過 throwing API,給 Optional 添加擴展,來解包它。這種情況下,我們要 Optional 為非空,或者就 throw 錯誤,如下:

extension Optional {
    func unwrapThrow(_ errorExpression: @autoclosure() -> Error) {
        guard let value = self else {
            throw errorExpression()
        }

        return value
    }
}

和 assert 的實現類似,這樣我們判斷錯誤表達式只在需要的時候,而不是每次解包 Optional時都判空。我們現在可以如下使用 unwrapOrThrow API:

let name = try argument(at: 1).unwrapOrThrow(ArgumentError.missingName)

使用默認值進行類型推斷#

@autoclosure 最後一個使用場景是,從 dictionary、database、UserDefaults 中提取一個可空的值時。

一般情況下,從一個不確定類型的 dictionary 中獲取一個屬性,並且提供一個默認值,需要寫如下代碼:

let coins = (dictionay["numberOfCoins"] as? Int) ?? 100

上面的代碼不僅難於閱讀,而且還有類型推斷和??運算符。我們可以使用 @autoclosure,定義一個 API,來實現同樣的表達:

let coins = dictionary.value(forKey: "numberOfCoins", defaultValue: 100)

如上,我們可以看出,defaultValue 一方面作為默認值用於值缺失時,另一方面也用於類型推斷,不需要特別聲明類型或者類型捕捉。簡潔明了👍

然後我們來一下怎麼樣定義一個這樣的 API:

extension Dictionary where Value = Any {
    func value<T>(forKey key: Key, defaultValue: @autoclosure () -> T) -> T {
        guard let value = self[key] as? T else {
            return defaultValue()
        }
        return value
    }
}

同樣的,我們使用 @autoclosure 避免了每次執行方法的時候都判斷 defaultValue。

結論#

減少冗長代碼是需要仔細考慮的。我們的目標是寫出能自我解釋的、易於閱讀的代碼,所以在設計低冗余 API 時,我們需要確定,沒有在調用中移除重要信息。

我認為恰當使用 @autoclosure,是實現上述目的的偉大工具。處理表達式、不僅僅是值,不僅可以減少冗長的代碼,也可以潛在的提高性能。

你認為 @autoclosure 還有其他使用情景嗎?或者有問題、評論、反饋,請聯繫我。Twitter: @johnsundell.

感謝閱讀!🚀

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。