Swift 的字串 API 似乎讓人難以習慣。此外,每次 Swift 與其標準庫版本更新的時候,字串的 API 也時不時會發生改變。你在 Stack Overflow 上尋找到的 Swift 1.2 解決方案往往不能在 Swift 2 上按照預期(甚至完全不能)使用。雖然從好的方面來看,我發現蘋果的官方文件是非常有用的(參見本文底部的連結),但是出於備查的目的以及為了幫助仍掙扎於其中的人們,在此我仍舊了列出一系列的 String 程式碼片段:
(Gist 和我 Github 倉庫中的 Playground 都已提供)
字串初始化
建立一個字串物件有無數種方式可以使用,包括字面量、從其他 Swift 型別轉換、Unicode等等。
1 2 3 4 5 6 7 8 9 10 11 |
var emptyString = "" // 空字串 var stillEmpty = String() // 另一種空字串的形式 let helloWorld = "Hello World!" // 字串字面量 let a = String(true) // 由布林值轉換為:"true" let b: Character = "A" // 顯式建立一個字元型別 let c = String(b) // 從字元 "A" 轉換 let d = String(3.14) // 從 Double 型別轉換為 "3.14" let e = String(1000) // 從 Int 型別轉換為 "1000" let f = "Result = \(d)" // 字串插值 "Result = 3.14" let g = "\u{2126}" // Unicode 字元,歐米伽符號 Ω let h = String(count:3, repeatedValue:b) // 重複字元組成的字串 "AAA" |
字串是值型別
字串是值型別(Value Type),當用其賦值或者函式傳參的時候它會被拷貝(copied)。所拷貝的值在修改的時候是懶載入的(lazy)。
1 2 3 4 |
var aString = "Hello" var bString = aString bString += " World!" // "Hello World!" print("(aString)") // "Hellon" |
字串檢測(空值、等值以及次序)
檢測一個字串是否為空:
1 |
emptyString.isEmpty // true |
Swift 是支援 Unicode 編碼的,因此相等運算子(”==”)將會判斷 Unicode 的正規化是否等價(canonical equivalence)。這意味著對於兩個字串來說,如果擁有相同的語義(linguistic meaning)和表現形式的話,即使它們由不同 Unicode 標量(scalar)組成,那麼也認為這兩個字串相等:
1 2 3 4 5 6 |
let spain = "Espa?a" let tilde = "u{303}" let country = "Espan" + "(tilde)" + "a" if country == spain { print("滿足匹配!") // "滿足匹配!n" } |
比較次序的話:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if "aaa" 字首/字尾檢測檢測一個字串是否擁有某個字首或者字尾:let line = "0001 這裡放上一些測試資料 %%%%" line.hasPrefix("0001") // true line.hasSuffix("%%%%") // true大小寫互相轉換顧名思義:let mixedCase = "AbcDef" let upper = mixedCase.uppercaseString // "ABCDEF" let lower = mixedCase.lowercaseString // "abcdef"字符集合字串並不是某種編碼的字符集合(collection views),但是它可以通過相應的屬性為不同的編碼形式提供所對應的字符集合。country.characters // characters country.unicodeScalars // Unicode scalar 21-bit codes country.utf16 // UTF-16 編碼 country.utf8 // UTF-8 編碼字元總數字符串並沒有一個直接的屬性用以返回其包含的字元總數,因為字元總數只對特定的編碼形式來說才有意義。因此,字元總數需要通過不同編碼的字符集合來訪問:// spain = Espa?a print("(spain.characters.count)") // 6 print("(spain.unicodeScalars.count)") // 6 print("(spain.utf16.count)") // 6 print("(spain.utf8.count)") // 7使用索引來訪問字符集合每個字符集合都擁有“索引”,可以通過它來訪問整個集合中的元素。這或許是在使用字串過程中碰到的最大難點之一了。你不能使用下標語法來訪問字串中的任意元素(比如說string[5])。要遍歷某個集合中的所有元素的時候(從現在開始我都將使用 characters 集合),可以通過 for...in 迴圈來進行:var sentence = "Never odd or even" for character in sentence.characters { print(character) }每個集合都有兩個例項屬性,你可以在集合中使用它們來進行索引,就如同下標語法哪樣: |
- startIndex:返回首個元素的位置,如果為空,那麼和 endIndex 的值相同。
- endIndex:返回字串逾尾(past the end)的位置。
注意到如果使用 endIndex 的話,就意味著你不能直接將其作為下標來進行使用,因為這會導致越界。
1 2 3 |
let cafe = "café" cafe.startIndex // 0 cafe.endIndex // 4 - 最後一個字元之後的位置 |
當通過以下幾種方法進行字串修改的時候,startIndex 和 endIndex 就變得極其有用:
- successor():獲取下一個元素
- predecessor():獲取上一個元素
- advancedBy(n):向前或者向後跳 n 個元素
下面是一些用例,注意到如果必要的話你可以將操作串聯起來:
1 2 3 4 5 6 7 |
cafe[cafe.startIndex] // "c" cafe[cafe.startIndex.successor()] // "a" cafe[cafe.startIndex.successor().successor()] // "f" // 注意到 cafe[endIndex] 會引發執行時錯誤 cafe[cafe.endIndex.predecessor()] // "é" cafe[cafe.startIndex.advancedBy(2)] // "f" |
Indices 屬性將返回字串中所有元素的範圍,這在遍歷集合的時候很有用:
1 2 3 |
for index in cafe.characters.indices { print(cafe[index]) } |
你無法使用某個字串中的索引來訪問另一個字串。你可以通過 distanceTo 方法將索引轉換為整數值:
1 2 3 4 5 |
let word1 = "ABCDEF" let word2 = "012345" let indexC = word1.startIndex.advancedBy(2) let distance = word1.startIndex.distanceTo(indexC) // 2 let digit = word2[word2.startIndex.advancedBy(distance)] // "2" |
範圍的使用
要檢出字串集合中某個範圍內的元素的話,可以使用範圍。範圍可以通過 start 和 end 索引來完成建立:
1 2 3 4 |
let fqdn = "useyourloaf.com" let rangeOfTLD = Range(start: fqdn.endIndex.advancedBy(-3), end: fqdn.endIndex) let tld = fqdn[rangeOfTLD] // "com" |
使用 “…” 或者 “..
通過索引或者範圍來擷取字串
要通過索引或者範圍來擷取字串的話,有許多方法:
獲取字首或者字尾
如果你需要得到或者拋棄字串前面或者後面的某些元素的話,可以:
1 2 3 4 5 6 7 8 9 10 11 12 |
let digits = "0123456789" let tail = String(digits.characters.dropFirst()) // "123456789" let less = String(digits.characters.dropFirst(3)) // "23456789" let head = String(digits.characters.dropLast(3)) // "0123456" let prefix = String(digits.characters.prefix(2)) // "01" let suffix = String(digits.characters.suffix(2)) // "89" let index4 = digits.startIndex.advancedBy(4) let thru4 = String(digits.characters.prefixThrough(index4)) // "01234" let upTo4 = String(digits.characters.prefixUpTo(index4)) // "0123" let from4 = String(digits.characters.suffixFrom(index4)) // "456789" |
插入或刪除
要在指定位置插入字元的話,可以通過索引:
1 2 3 |
var stars = "******" stars.insert("X", atIndex: stars.startIndex.advancedBy(3)) // "***X***" |
要在索引出插入字串的話,那麼需要將字串轉換為字符集:
1 2 |
stars.insertContentsOf("YZ".characters, at: stars.endIndex.advancedBy(-3)) // "***XYZ***" |
範圍替換
要替換一個範圍的字串內容的話:
新增元素
可以通過“+”運算子將字串相互連線起來,也可以使用 appendContentsOf 方法:
1 2 3 |
var message = "Welcome" message += " Tim" // "Welcome Tim" message.appendContentsOf("!!!") // "Welcome Tim!!! |
移除或者返回指定索引的元素
從一個字串當中移除某個元素,需要注意這個方法將會使該字串此前所有的任何索引標記(indice)失效。
1 2 3 |
var grades = "ABCDEF" let ch = grades.removeAtIndex(grades.startIndex) // "A" print(grades) // "BCDEF" |
範圍移除
移除字符集中某個範圍的字元,需要主要的是這個方法同樣也會使索引標記失效:
1 2 3 |
var sequences = "ABA BBA ABC" let midRange = sequences.startIndex.advancedBy(4)...sequences.endIndex.advancedBy(-4) sequences.removeRange(midRange) // "ABA ABC" |
與 NSString 橋接
String 可以轉換為 NSString 從而與 Objective-C 橋接。如果 Swift 標準庫沒有你所需要的功能的話,那麼匯入 Foundation 框架,通過 NSString 來訪問這些你所需要的方法。
請注意這個橋接方法並不是無損的,因此儘可能使用 Swift 標準庫完成大部分功能。
1 2 3 4 |
// 不要忘記匯入 Foundation import Foundation let welcome = "hello world!" welcome.capitalizedString // "Hello World!" |
檢索內含的字串
使用 NSString 方法的一個例子就是執行內含字串的檢索:
1 2 3 4 5 6 |
let text = "123045780984" if let rangeOfZero = text.rangeOfString("0", options: NSStringCompareOptions.BackwardsSearch) { // 尋找“0”元素,然後獲取之後的元素 let suffix = String(text.characters.suffixFrom(rangeOfZero.endIndex)) // "984" } |
Playgournd
我發現在 Xcode 中通過 Playground 來熟悉 API 是一個非常好的選擇。如果你想要搶先體驗一下所有這些功能的話,這個文章的 Playground 可以從我的 Github 倉庫中下載。