在 Swift 5 中如何使用原始字串

知識小集發表於2019-01-28

| 作者:Paul Hudson

| 原文連結

| 公眾號連結

Swift 5 中的原始字串(raw string)讓我們能夠編寫出更自然的字串,尤其是在使用反斜槓引號時。正如將在下面看到的那樣,在某些情況下效果是很明顯的,如正規表示式。

我之前在 What’s new in Swift 5.0 一文中介紹了 Swift 5 中的所有新功能,甚至還有一個專門用於跟蹤 Swift 最新功能的網站。在本文中我想談談如何在 Swift 5 中使用原始字串,同時還提供了一些有用的詳細示例,以便更清楚地瞭解它們的用處。

如果你曾經問過自己“Swift 中的那些帶標籤 # 的字串是什麼?” 的話,你應該能在這篇文章中找到答案!

提示:原始字串是完全可選的 - 重要的是你至少知道它們是什麼,這樣你就可以在某段程式碼中看到它們時知道這是什麼,而不一定需要在你自己的程式碼中使用它們。

什麼是原始字串?

在 Swift 5 中,我們能夠使用 # 符號來指定自定義字串的分割符。 當我們使用帶 # 的字串時,它會影響到 Swift 解析字串中特殊字元的方式:\ 不再作為轉義字元,所以 \n 字面意思是反斜槓跟著 “n” 而不是換行符,而 \(variable) 不再表示字串插值,而是實實在在的字串。

這樣,以下兩個字串是相同的:

let regularString = "\\Hello \\World"
let rawString = #"\Hello \World"#
複製程式碼

請注意,在第二個示例中,字串以 # 開頭和結尾,這標誌著它是一個原始字串。

現在可以在字串內使用相同的 # 符號,用於標記特殊字元。例如,如果要使用字串插值,現在應該使用 \#(variableName) 而不是 \(variableName),如下所示:

let name = "Taylor"
let greeting = #"Hello, \#(name)!"#
print(greeting)
複製程式碼

我們也可以將 # 與多行字串一起使用,如下所示:

let message = #"""
This is rendered as text: \(example).
This uses string interpolation: \#(example).
"""#
複製程式碼

使用分隔符

雖然這是理論上應該永遠不需要的功能,但可以在字串周圍新增更多 #,以建立更多的唯一的字串分隔符。

例如,以下這些都建立相同的字串:

let zero = "This is a string"
let one = #"This is a string"#
let two = ##"This is a string"##
let three = ###"This is a string"###
let four = ####"This is a string"####
複製程式碼

這種情況存在的原因是我們想根據自己的需要來結束字串,這樣當你需要在字串中使用 "# 這種比較少的情形時,也不會遇到問題。

應該強調的是,這種情況非常少見。例如,你想寫一個字串,如 My dog said "woof"#gooddog -- 注意在 "woof" 後面沒有空格,後面直接跟了一個 Twitter 風格的標籤 #gooddog。如果只使用單個分割符的原始字串,Swift 會將 #gooddog 中的 # 視為結束符,所以我們需要如下處理:

let str = ##"My dog said "woof"#gooddog"##
複製程式碼

為什麼原始字串有用?

Swift Evolution 在原始字串的 proposal 中列出了三個使用原始字串的例子。具體來說,是以下情形的程式碼:

  • 被轉義掩蓋了。轉義會損害程式碼審查和驗證。
  • 已經轉義了。轉義的內容不應由編譯器預先解釋。
  • 無論是為了測試還是僅更新源,都需要在源和程式碼之間輕鬆傳輸。

前兩個是最有可能影響你的:向已經轉義的字串新增轉義通常會使程式碼更難以閱讀。

作為一個例子,讓我們來看看正規表示式。假設我們有一個像這樣的字串:

let message = #"String interpolation looks like this: \(age)."#
複製程式碼

這裡使用原始字串來展示字串插值的語義而不是實際使用它 - 字串 (age) 將出現在文字中,而不是被 age 的實際值替換。

如果我們想要建立一個正規表示式來查詢所有字串插值,我們將以 \([^)]) 開頭。這表示著“反斜槓,左括號,一個或多個不是右括號的字元,然後是右括號。(如果你還沒有使用達正規表示式,建議看下 Beyond Code 這本書

但是,我們不能在 Swift 中使用它 - 因為這是無效的:

let regex = try NSRegularExpression(pattern: "\([^)])")
複製程式碼

Swift將 \ 視為轉義字元,並假定我們正在嘗試在正規表示式中使用字串插值。所以,我們需要兩個反斜槓來做轉義,如下所示:

let regex = try NSRegularExpression(pattern: "\\([^)]+)")
複製程式碼

But now there’s a second problem: when that string reaches the regex system it will be read as ([^)]), so the regex system will assume we’re escaping the opening parenthesis as opposed to typing a literal backslash, so we need to add another escape for the regex system:

但現在又有第二個問題:當正規表示式系統處理該字串時,會將 \([^]]) 作為輸入,因此正規表示式系統將假設我們正在轉義左括號而不是將 \ 當作文字處理,所以我們需要為正規表示式系統新增另一個轉義:

let regex = try NSRegularExpression(pattern: "\\\([^)]+)")
複製程式碼

而這時 Swift 又會抱怨,因為它認為我們要同時轉義反斜槓並括號,所以我們需要第四個反斜槓:

let regex = try NSRegularExpression(pattern: "\\\\([^)]+)")
複製程式碼

是的,現在有四個反斜槓:一個是我們想要匹配的,一個是在 Swift 中用於轉義的,一個是在正規表示式引擎中用於轉義的,另一個是轉義正在使用 Swift 中的一個正規表示式引擎(太繞)。

然而這個正規表示式仍然無法正常使用。

你看,我們還需要轉義我們想要匹配的左括號和右括號,這意味著完整的正規表示式是這樣的:

let regex = try NSRegularExpression(pattern: "\\\\\\([^)]+\\)")
複製程式碼

請記住,我們在正規表示式引擎中新增 \ 以轉義 ( ,同時在 Swift 中也要新增了一個 \ 以轉義正規表示式的引用。

現在來看看這個悲慘的結果:

在 Swift 5 中如何使用原始字串

如果我們使用原始字串,我們仍然需要轉義正規表示式引擎的字元:為了匹配 \ 我們必須寫 \,為了匹配 ( 我們必須寫 (。但是,至少我們不再需要為Swift新增額外的轉義字元。

所以,我們最終只需要一半的 \

let regex = try NSRegularExpression(pattern: #"\\\([^)]+\)"#)
複製程式碼

該正規表示式模式沒有 Swift 獨有的轉義,因此您可以在 regex101.com 等網站上試用它而無需修改。

下一步

要了解有關 Swift 5 中新功能的更多資訊,您可以閱讀我的文章:What’s new in Swift 5.0?

閱讀下 Swift Evolution 關於原始字串的 proposal 會得到更多的資訊:SE-0200 – Enhancing String Literals Delimiters to Support Raw Text

最後,我強烈推薦 Erica Sadun 關於同一主題的文章(SE-0200 – Enhancing String Literals Delimiters to Support Raw Text.)。 Erica 在這個 proposal 方面發揮了重要作用,並且對如何有效地使用原始字串提出了很多很好的建議。

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題(加微信 coldlight_hh)。

在 Swift 5 中如何使用原始字串

相關文章