原文鏈接:Using @autoclosure when designing Swift APIs
Swift的@autoclosure屬性能讓你定義一個自動被閉包的參數(shù)。它的主要作用是推遲表達(dá)式(可能代價高昂)的執(zhí)行,從而避免在傳遞參數(shù)時就直接執(zhí)行。
assert
在Swift標(biāo)準(zhǔn)庫中的assert函數(shù)就使用了@autoclosure屬性。我們都知道斷言只會在debug模式下被觸發(fā),因此在release模式下是沒有必要執(zhí)行斷言表達(dá)式的。assert定義如下
func assert(_ expression: @autoclosure () -> Bool,
_ message: @autoclosure () -> String) {
guard isDebug else {
return
}
// 在assert內(nèi)部,我們可以把表達(dá)式當(dāng)做一個正常的閉包來使用
if !expression() {
assertFailure(message())
}
}
上面是我簡單模擬assert的實(shí)現(xiàn),真正的實(shí)現(xiàn)在這里
@autoclosure的好處在于它對調(diào)用方?jīng)]有任何影響。如果assert使用正常的閉包來定義的話,那么你就必須這么使用它:
assert({ someCondition() }, { "Hey, it failed!" })
但是現(xiàn)在,你可以像調(diào)用非閉包參數(shù)一樣調(diào)用它:
assert(someCondition(), "Hey it failed!")
接下來,讓我們來看看如何在自己的代碼中使用@autoclosure屬性,來使我們的API更友好。
內(nèi)聯(lián)賦值
@autoclosure可以在函數(shù)調(diào)用中內(nèi)聯(lián)表達(dá)式。我們能利用它做一些事情,比如傳遞賦值表達(dá)式作為參數(shù)。我們看看下面這個可能有用的例子。
UIView.animate(withDuration: 0.25) {
view.frame.origin.y = 100
}
使用@autoclosure,我們可以編寫一個自動創(chuàng)建動畫閉包并執(zhí)行它的動畫函數(shù),如下所示:
func animate(_ animation: @autoclosure(escaping) () -> (),
duration: TimeInterval = 0.25) {
UIView.animate(withDuration: duration, animations: animation)
}
現(xiàn)在我們可以使用簡單的函數(shù)調(diào)用來執(zhí)行動畫,而不需要額外的{}語法:
animate(view.frame.origin.y = 100)
使用@autoclosure,我們可以真正減少動畫代碼的冗長度,而不會犧牲可讀性或變現(xiàn)力??。
使用表達(dá)式傳遞錯誤
我發(fā)現(xiàn)@autoclosure的另一個非常有用的情況:編寫處理錯誤的代碼。比如,假設(shè)我們要在Optional上添加一個擴(kuò)展,使我們能夠在解包它出錯時拋出異常。這樣我們可以要求Optional是非nil,否則將拋出異常。如下所示:
extension Optional {
func unwrapOrThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
guard let value = self else {
throw errorExpression()
}
return value
}
}
類似于assert的實(shí)現(xiàn),我們只會在需要的時候執(zhí)行表達(dá)式,而不是每次嘗試解包時都要執(zhí)行?,F(xiàn)在我們可以像這樣使用我們的unwrapOrThrow:
let name = try argument(at: 1).unwrapOrThrow(ArgumentError.missingName)
使用默認(rèn)值做類型推斷
我發(fā)現(xiàn)的最后的使用場景是從dictionary,database或者UserDefaults中提取可選值。
通常,當(dāng)從一個沒有指明特定類型的字典中提取一個值并提供一個默認(rèn)值時,你必須這么寫:
let coins = (dictionary["numberOfCoins"] as? Int) ?? 100
這種方式難以閱讀,并且有很多復(fù)雜的語法糖。使用@autoclosure,我們可以定義一個API,來讓我們想下面這樣實(shí)現(xiàn)同樣的功能:
let coins = dictionary.value(forKey: "numberOfCoins", defaultValue: 100)
從上面,我們可以看到默認(rèn)值可以拿來做類型推斷,而不需要指定類型來完成類型轉(zhuǎn)換。很簡潔??
讓我們來看看如何編寫這個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
}
}
再次強(qiáng)調(diào),我們使用@autoclosure來避免每次調(diào)用方法是都執(zhí)行默認(rèn)值表達(dá)式。
結(jié)論
減少冗長總是需要仔細(xì)考慮的事情。 我們的目標(biāo)應(yīng)該始終是編寫富有表現(xiàn)力,易于閱讀的代碼,所以我們需要確保在設(shè)計低冗余性API時不會在使用時丟掉重要信息。
我認(rèn)為在適當(dāng)?shù)那闆r下使用@autoclosure是一個很好的工具。用表達(dá)式代替數(shù)值,使我們能夠減少冗長和多余,同時也可能獲得更好的性能。
感謝閱讀! ??