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)" ) // "Hello\n" |
字符串檢測(空值、等值以及次序)
檢測一個字符串是否爲空:
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
|
if "aaa" < "bbb" { print( "aaa" ) } |
前綴/後綴檢測
檢測一個字符串是否擁有某個前綴或者後綴:
1
2
3
|
let line = "0001 這裏放上一些測試數據 %%%%" line.hasPrefix( "0001" ) // true line.hasSuffix( "%%%%" ) // true |
大小寫互相轉換
顧名思義:
1
2
3
|
let mixedCase = "AbcDef" let upper = mixedCase.uppercaseString // "ABCDEF" let lower = mixedCase.lowercaseString // "abcdef" |
字符集合
字符串並不是某種編碼的字符集合(collection views),但是它可以通過相應的屬性爲不同的編碼形式提供所對應的字符集合。
1
2
3
4
|
country.characters // characters country.unicodeScalars // Unicode scalar 21-bit codes country.utf16 // UTF-16 編碼 country.utf8 // UTF-8 編碼 |
字符總數
字符串並沒有一個直接的屬性用以返回其包含的字符總數,因爲字符總數只對特定的編碼形式來說纔有意義。因此,字符總數需要通過不同編碼的字符集合來訪問:
1
2
3
4
5
|
// 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 循環來進行:
1
2
3
4
|
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 倉庫中下載。