| 作者:Suragch
以下所有示例都基於下面這行程式碼:
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,也歡迎加入我們的群組討論問題。可以公眾號留言 ios
、flutter
等關鍵詞獲取入群方式。