作者:Olivier Halligon,原文連結,原文日期:2018-12-15
譯者:Nemocdz;校對:numbbbbb,Yousanflics;定稿:Forelax
StringInterpolation
協議最初的設計效率低下又不易擴充套件,為了在後續的版本中能夠將其徹底重構,Swift 4 中將該協議標記為廢棄。即將在 Swift 5 中亮相的 SE-0228 提案介紹了一種新的 StringInterpolation
設計,使得 String 有了更大的潛能。
在 Swift 的 master
分支裡實現之後,就可以下載一個 快照 來安裝最新的 Swift 5 工具鏈到 Xcode 中,來嘗試全新的 StringInterpolation
。讓我們來把玩一下。
全新的 StringInterpolation 設計
我強烈建議本篇文章的讀者閱讀一下 SE-0228 提案,感受一下新 API 的背後的設計思路和動機。
要讓一個型別遵循 ExpressibleByStringInterpolation
,最基本的你需要:
- 讓這個型別擁有一個型別為
StringInterpolation
的子型別,這個子型別遵循StringInterpolationProtocol
並將負責解釋插值 - 這個子型別僅需要實現
appendLiteral(_ literal: String)
方法,再選擇一個或多個你自己想要支援的appendInterpolation(...)
簽名的方法 - 這個
StringInterpolation
子型別會作為“構造器”服務於你的主型別,然後編譯器會呼叫那些append…
方法一步一步地構造物件 - 然後你的主型別需要實現
init(stringInterpolation: StringInterpolation)
,它會用上一步的結果來例項化它自己。
你可以實現任何你喜歡的 appenInterpolation(...)
方法,這意味著你可以任意選擇支援什麼插值。這是一個帶來巨大的可能性的超強功能。
舉個例子,如果你實現了 func appendInterpolation(_ string: String, pad: Int)
,那麼意味著你將可以用類似這樣的插值:"Hello \(name, pad: 10), how are you?"
來構造你的型別。插值只需要匹配你的 StringInterpolation
子型別其中一個支援的 appendInterpolation
方法簽名。
一個簡單的例子
讓我用一個簡單的例子來演示一下插值是如何運作的。一起來構造一個允許引用 issue 編號和使用者的 GitHubComment
型別吧。
這個例子的目標是做到類似下面的寫法:
let comment: GitHubComment = """
See \(issue: 123) where \(user: "alisoftware") explains the steps to reproduce.
"""
複製程式碼
所以我們該怎麼實現它呢?
首先,讓我們宣告基本的結構體 struct GitHubComment
並讓它遵循 ExpressibleByStringLiteral
(因為 ExpressibleByStringInterpolation
繼承自這個協議所以我們將它的實現抽離)和 CustomStringConvertible
(為了 debug 時友好地在控制檯中列印)。
struct GitHubComment {
let markdown: String
}
extension GitHubComment: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.markdown = value
}
}
extension GitHubComment: CustomStringConvertible {
var description: String {
return self.markdown
}
}
複製程式碼
然後,我們讓 GitHubComment
遵循 ExpressibleByStringInterpolation
。這意味著在剩下需要實現的功能,將由一個 StringInterpolation
子型別來完成:
-
首先初始化它自己:
init(literalCapacity: Int, interpolationCount: Int)
提供給你保留一部分資料到緩衝區的能力,在一步步構造型別時就會用到這個能力。在這個例子中,我們可以在構造例項的時候,簡單用一個String
並往它上面追加片段,不過這裡我採用一個parts: [String]
來代替,之後再將它組合起來 -
實現
appendLiteral(_ string: String)
逐個追加文字到parts
裡 -
實現
appendInterpolation(user: String)
在遇到\(user: xxx)
時逐個追加 markdown 表示的使用者配置連結 -
實現
appendInterpolation(issue: Int)
逐個追加用 markdown 表示的 issue 連結 -
然後在
GitHubComment
上實現init(stringInterpolation: StringInterpolation)
將parts
構造成一個評論
extension GitHubComment: ExpressibleByStringInterpolation {
struct StringInterpolation: StringInterpolationProtocol {
var parts: [String]
init(literalCapacity: Int, interpolationCount: Int) {
self.parts = []
// - literalCapacity 文字片段的字元數 (L)
// - interpolationCount 插值片段數 (I)
// 我們預計通常結構會是像 "LILILIL"
// — e.g. "Hello \(world, .color(.blue))!" — 因此是 2n+1
self.parts.reserveCapacity(2*interpolationCount+1)
}
mutating func appendLiteral(_ literal: String) {
self.parts.append(literal)
}
mutating func appendInterpolation(user name: String) {
self.parts.append("[\(name)](https://github.com/\(name))")
}
mutating func appendInterpolation(issue number: Int) {
self.parts.append("[#\(number)](issues/\(number))")
}
}
init(stringInterpolation: StringInterpolation) {
self.markdown = stringInterpolation.parts.joined()
}
}
複製程式碼
這就完事了!我們成功了!
注意,因為那些我們實現了的 appendInterpolation
方法簽名,我們允許使用 Hello \(user: "alisoftware")
但不能使用 Hello \(user: 123)
,因為 appendInterpolation(user:)
期望一個 String
作為形參。類似的是,在你的字串中 \(issue: 123)
只能允許一個 Int
因為 appendInterpolation(issue:)
採用一個 Int
作為形參。
實際上,如果你嘗試在你的 StringInterpolation
子類中用不支援的插值,編譯器會給你提示報錯:
let comment: GitHubComment = """
See \(issue: "bob") where \(username: "alisoftware") explains the steps to reproduce.
"""
// ^~~~~ ^~~~~~~~~
// 錯誤: 無法轉換 ‘String’ 型別的值到期望的形參型別 ‘Int’
// 錯誤: 呼叫 (have 'username:', expected 'user:')實參標籤不正確
複製程式碼
這僅僅只是個開始
這個新的設計開啟了一大串腦洞讓你去實現自己的 ExpressibleByStringInterpolation
型別。這些想法包括:
- 建立一個
HTML
型別並遵循,你就可以用插值寫 HTML - 建立一個
SQLStatement
型別並遵循,你就可以寫更簡單的 SQL 語句 - 用字串插值支援更多自定義格式,比如在你的插值字串中用格式化
Double
或者Date
值 - 建立一個
RegEX
型別並遵循,你就可以用花裡胡哨的語法寫正規表示式 - 建立一個
AttributedString
型別並遵循,就可以用字串插值構建NSAttributedString
帶來新的字串插值設計的 Brent Royal-Gordon 和 Michael Ilseman,提供了更多例子在這個 要點列表 中。
我個人嘗試了一下支援 NSAttributedString
的實現,並想 在專門的一篇文章裡分享它的初步實現,因為我發現它非常優雅。我們下一篇文章再見!
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 swift.gg。