Swift 中的 String 和 Substring 如何工作

知識小集發表於2019-03-02

| 作者:Suragch

| medium.com/@studymongo…

以下所有示例都基於下面這行程式碼:

var str = "Hello, playground"
複製程式碼

字串與子字串

字串在不同的 Swift 版本中變化比較大。在 Swift 4 中,當需要從一個字串中獲取子串時,我們獲取到的是 Substring 型別而不是一個 String 型別的值。為什麼會這樣?在 Swift 中,字串是值型別。這意味著如果想從一個字串中生成一個新的字串,則需要做拷貝處理。這對於穩定性是有益的,但效率卻不高。

從另一方面講,一個 Substring 則是對其原始 String 的一個引用。它不需要做拷貝操作,所以更加高效。但是有另一個問題,假設我們要從一個很長的字串中獲取一個長度為 10 的 Substring,由於這個 Substring 會引用 String,那麼只要 Substring 一直存在,String 就必須一直存在。所以,任何時候當處理完 Substring 後,需要將其轉換為 String。

let myString = String(mySubstring)
複製程式碼

這樣只會拷貝子串,而原來的字串可以被正確地回收。Substring 作為一種型別,本身即表示臨時存在的。

在 Swift 4 另一個大的改進是字串又成為集合了。這意味著在集合上的所有操作,也可以應用在字串中(如下標、迭代字元、過濾等)。

String.Index

在我們更多地瞭解子字串之前,瞭解 String 索引如何作用於字串中的字元會有很大的幫助。

startIndex 和 endIndex

  • startIndex 是開始字元的索引
  • endIndex 是結束字元的索引

示例

var str = "Hello, playground"
// character
str[str.startIndex] // H
str[str.endIndex]   // error: after last character
// range
let range = str.startIndex..<str.endIndex
str[range]  // "Hello, playground"
複製程式碼

基於 Swift 4 的單側範圍(one-side ranges)功能,可將範圍簡化為以下形式之一。

let range = str.startIndex...
let range = ..<str.endIndex
複製程式碼

為了清晰起見,在下面的示例中,我將使用完整形式,不過為了更可讀,你可能在你的程式碼中傾向於使用單側範圍。

after

如在 index(after: String.Index)

  • after 指的是給定索引後面的字元索引。

示例:

// character
let index = str.index(after: str.startIndex)
str[index]  // "e"
// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range]  // "ello, playground"
複製程式碼

before

如在 index(before: String.Index)

  • before 指的是給定索引前面的字元索引。

示例

// character
let index = str.index(before: str.endIndex)
str[index]  // d
// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range]  // Hello, playgroun
複製程式碼

offsetBy

如在 index(String.Index, offsetBy: String.IndexDistance)

  • offsetBy 的值可為正也可為負,以指定索引為起始位置。雖然它的型別是 String.IndexDistance,但可以給它一個 Int 值

示例

// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index]  // p
// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range]  // play
複製程式碼

limitedBy

如在 index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • limitedBy 可用於確保偏移量不會導致索引超出範圍。這是一個有界的索引。由於偏移量可能超出限制,因此此方法返回 Optional。如果索引超出範圍,則返回 nil。

示例:

// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
    str[index]  // p
}
複製程式碼

如果偏移量為 77 而不是 7,那麼將跳過 if 語句。

獲取子字串

我們可以使用下標或許多其他方法(例如,字首,字尾,拆分)從字串中獲取子字串。但是,我們仍然需要使用 String.Index 而不是 Int 值來指定索引範圍。

字串的起始值

我們可以使用下標(注意 Swift 4 的單側範圍):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello
複製程式碼

或者 prefix

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello
複製程式碼

或者更簡單的方式:

let mySubstring = str.prefix(5) // Hello
複製程式碼

字串的結束值

使用下標:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground
複製程式碼

或者 suffix

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground
複製程式碼

或者更簡單的方式:

let mySubstring = str.suffix(10) // playground
複製程式碼

請注意,當使用 suffix(from: index) 時,我必須使用 -10 從最後算起。 當只使用 suffix(x) 時,則不需要,字尾只佔用字串的最後 x 個字元。

字串的範圍

我們再次使用下標:

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
let mySubstring = str[range]  // play
複製程式碼

將 Substring 轉換為 String

不要忘記,當您準備儲存子字串時,應將其轉換為 String,以便清除舊字串的記憶體。

let myString = String(mySubstring)
複製程式碼

使用 Int 索引擴充套件

對字串使用 Int 索引要容易得多。實際上,通過使用基於 Int 的擴充套件,可以隱藏 String 索引的複雜性。然而,在讀完 Airspeed Velocity 和 Ole Begemann 的文章 Strings in Swift 3 後,我猶豫不決。此外,Swift 團隊故意沒有使用 Int 索引,而仍然使用 String.Index。原因是 Swift 中的 Character 在底層的長度並不完全相同。單個 Swift 字元可能由一個、兩個甚至更多 Unicode 組成。 因此,每個唯一的 String 必須計算其 Character 的索引。

不得不說,我希望 Swift 團隊找到一種方法來在將來抽象出String.Index。但在此之前,我選擇使用他們的 API。它幫助我記住 String 操作不僅僅是簡單的 Int 索引查詢。

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題。可以公眾號留言 iosflutter 等關鍵詞獲取入群方式。

Swift 中的 String 和 Substring 如何工作

相關文章