原文: Optionals and String Interpolation
做者: Ole Begemann
譯者: kemchenjhtml
你知道這個問題嗎? 你想要在 UI 上顯示一個 Optional 值, 或者是在控制檯打印出來, 但你不喜歡默認的 Optional 字符串的顯示方式: "Optional(...)" 或者是 "nil". 例如:git
var someValue: Int? = 5 print("這個值是 \(someValue)") // "這個值是 Optional(5)" someValue = nil print("這個值是 \(someValue)") // "這個值是 nil"
Swift 3.1 會在你往字符串裏插入一個 Optional 值的時候發出一個警告, 由於這個行爲可能會產生意料以外的結果. 這裏有 Julio Carrettoni, Harlan Haskins 和 Robert Widmann 在 Swift-Evolution 的討論:github
因爲 Optional 值永遠都不該該顯示給終端用戶, 而它又常常做爲一個控制檯裏的驚喜存在, 咱們以爲獲取一個 Optional 值的 debug 信息是一種"明確"的藝術. 提案目前的主要內容是, 在一個字符串片斷裏使用 Optional 值的時候須要發出一個警告.swift
在最新的 Swift 開發版本(2016-12-01)裏已經實現了這個警告:c#
你有幾個方法能夠去掉這個警告:api
添加一個顯式轉換, 例如 someValue as Int?
安全
使用 String(describing: someValue)
函數app
提供一個默認值去讓表達式不爲 Optional, 例如 someValue ?? defaultValue
(一種解包形式)框架
上面的方式我都不是特別喜歡, 但這是編譯器能提供的最好的方式了. 第三種作法的問題是解包操做符 ??
須要符合相應的類型 - 若是 ??
左邊的類型是 T?
的話, 那右邊的類型就必須是 T
. 用上面的例子來描述的話, 就意味着我只可以提供一個 Int
來做爲默認值, 而不能是一個字符串, 在這種狀況下就達不到我想要的效果.函數
我經過自定義一個字符串解包操做符來解決這個問題. 由於它來源於 ??
, 因此我決定把它命名爲 ???
. ???
操做符的左邊是 Optional 值, 而在右邊就是這個 Optional 值的字符串默認值, 返回一個字符串. 若是這個 Optional 值是 non-nil 的, 那麼它就會解包而後返回這個值的字符串描述, 不然就會返回一個默認值, 下面是具體的實現:
infix operator ???: NilCoalescingPrecedence public func ???<T>(optional: T?, defaultValue: @autoclosure () -> String) -> String { switch optional { case let value?: return String(describing: value) case nil: return defaultValue() } }
@autoclosure 結構保證了右邊的值只會在須要的時候纔會被計算出來, 例如 Optional 值是 nil 的時候. 這就可讓你傳遞一個複雜的或者耗時的運算表達式進去, 而只會在特定狀況下才會影響到性能. 我不認爲這種狀況(表達式很複雜)會常常發生, 但它是參考了 ?? 操做符在標準庫裏的實現.(儘管我決定去掉標準庫實現裏的 throws/rethrows)
或者, 你能夠經過 Optional.map 只用一行代碼來實現這個操做符, 就像這樣:
public func ???<T>(optional: T?, defaultValue: @autoclosure () -> String) -> String { return optional.map { String(describing: $0) } ?? defaultValue() }
這跟第一個實現的效果如出一轍, 用哪個只看你我的的口味和代碼習慣. 我不認爲哪個比另外一個更加清晰.
最後一件我想說的是, 你必須從 String(describing:) (更偏向於值的描述) 或者是 String(reflecting:) (更偏向於 debug 信息) 中作出一個選擇, 去轉化這個值. 前一個選擇更適合 UI 展現, 然後一個則更適合運行日誌. 甚至你能夠再自定義一個操做符 (例如: ????
), 去適應平常 debug 需求.
咱們使用 ???
操做符來重構一下文章最開始的那個例子:
var someValue: Int? = 5 print("值是 \(someValue ??? "unknown")") // "值是 5" someValue = nil print("值是 \(someValue ??? "unknown")") // "值是 unknown"
這是一個很小的改變, 但我很喜歡
[1]. 我最開始其實以爲重載 ??
就行了. 我喜歡這種方式是由於個人視線更加符合解包符號的含義, 但這也會在某些狀況下失去了類型安全的優勢, 由於老是會被編譯成 someOptional ?? "someValue"
的形式
我想特別說明一點是, 在咱們的項目裏, 重載 ??
或者是自定義操做符 ???
其實是不會影響到咱們引入的庫的, 咱們定義的 ??
和 ???
都是默認 internal
的, 也就是說做用域只在 Module 內, 怎麼用都是沒問題的. 固然, 若是是多人協做的狀況就要權衡溝通成本和實際帶來便捷了.
若是是咱們本身想寫框架的話, 聲明爲 internal
, 而後就放心大膽的用吧, 不會污染到外部做用域的
但我不太肯定聲明爲 public
的話會發生什麼事情, 根據我在 medium 上看到的這篇文章, 至少在 Swift 1.0 的時候, 這麼作是真的會污染全局的, Swift 團隊後來也沒提到過對於這樣的作法有怎樣的優化, 因此我估計仍是會污染全局的
若是有了解的人, 或者已經作過測試的人, 能夠的話告訴一下我準確的結果