Optional 與 字符串 的交互

原文: Optionals and String Interpolation
作者: Ole Begemann
譯者: kemchenj

你知道這個問題嗎? 你想要在 UI 上顯示一個 Optional 值, 或者是在控制臺打印出來, 但你不喜歡默認的 Optional 字符串的顯示方式: "Optional(...)" 或者是 "nil". 例如:

var someValue: Int? = 5
print("這個值是 \(someValue)")
// "這個值是 Optional(5)"

someValue = nil
print("這個值是 \(someValue)")
// "這個值是 nil"

在字符串里插入 Optional 值會有一些不可預料的結果

Swift 3.1 會在你往字符串里插入一個 Optional 值的時候發出一個警告, 因為這個行為可能會產生意料之外的結果. 這里有 Julio Carrettoni, Harlan Haskins 和 Robert Widmann 在 Swift-Evolution 的討論:

由于 Optional 值永遠都不應該顯示給終端用戶, 而它又經常作為一個控制臺里的驚喜存在, 我們覺得獲取一個 Optional 值的 debug 信息是一種"明確"的藝術. 提案目前的主要內容是, 在一個字符串片段里使用 Optional 值的時候需要發出一個警告.

在最新的 Swift 開發版本(2016-12-01)里已經實現了這個警告:

14813802446445.png

你有幾個方法可以去掉這個警告:

  1. 添加一個顯式轉換, 例如 someValue as Int?
  2. 使用 String(describing: someValue)函數
  3. 提供一個默認值去讓表達式不為 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 團隊后來也沒提到過對于這樣的做法有怎樣的優化, 所以我估計還是會污染全局的

如果有了解的人, 或者已經做過測試的人, 可以的話告訴一下我準確的結果

相關閱讀

emptiness
將可選類型轉換為錯誤拋出

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,830評論 18 139
  • 對各種值為"空"的情況處理不當,幾乎是所有Bug的來源。 在我們的例子里,盡管tmp的值是nil,但調用tmp的r...
    AKyS佐毅閱讀 10,544評論 1 13
  • 1.元組 1.1什么是元組 在其他語言中很早就有元組這個概念, 但是對于OC程序員來說這是一個新的概念官方定義:元...
    高俊閱讀 440評論 0 0
  • 文\破曉晨曦 1 筱筱覺得,最狠的相遇,不過是久別重逢。 小雨的下午,筱筱匆忙地趕去約好的咖啡店,遠遠的走來一個人...
    破曉晨曦閱讀 547評論 0 5
  • 股票學,其實就是概率學 我們在市場,為什么賺錢,為什么虧錢,賺錢的人為什么一直賺錢,虧錢的人為什么一直虧錢,為什么...
    厚尚環保芮寧閱讀 940評論 0 1