一:swift字符串特性
個(gè)人理解Unicode是比較符合人的思維而設(shè)計(jì)的,一串復(fù)雜的字符,一眼看上去是幾個(gè)長(zhǎng)度(由幾塊組成),和實(shí)際上描述這個(gè)Unicode的數(shù)據(jù)長(zhǎng)度完全沒(méi)關(guān)系.因?yàn)閷?duì)編程語(yǔ)言來(lái)說(shuō),Unicode的內(nèi)在復(fù)雜度究極折磨.
大部分語(yǔ)言的字符串類型可以存儲(chǔ)Unicode 數(shù)據(jù) ,但是它沒(méi)有保證像是獲取字符串?度這類簡(jiǎn)單操作會(huì)返回 “恰當(dāng)” 的結(jié)果,而swift就在致力于實(shí)現(xiàn)這種Unicode正確性.
Swift 中的 String是 Character 值的集合,而 Character 是人類在閱讀文字時(shí)所理解的單個(gè)字符,這與該字符由多少個(gè) Unicode 碼點(diǎn)組成無(wú)關(guān),也就是前面說(shuō)的即使是非常復(fù)雜的字符串,一眼看起來(lái)是幾塊,swift會(huì)像人一樣去分成幾個(gè)Character.
那么代價(jià)是什么呢.
代價(jià)就是swift字符串的復(fù)雜度上升了(悲),str[999] 不能獲取字符串的第一千個(gè)字符;str[idx+1] 不能訪問(wèn)到下一個(gè)字符;當(dāng)字符擁有可變寬度時(shí),字符串并不知道第 n 個(gè)字符到底存儲(chǔ)在哪兒,它必須查看這個(gè)字符前面的所有字符,才能最終確定對(duì)象字符的存儲(chǔ)位置,所以這不是一個(gè) O(1) 操作.
今天的 Unicode 是一個(gè)可變?格式。它的可變?特性有兩種不同的意義:由編碼單元(code unit) 組成 Unicode 標(biāo)量 (Unicode scalar);由 Unicode 標(biāo)量組成字符.
Unicode 數(shù)據(jù)可以被編碼成許多不同寬度的編碼單元,最普遍的使用的是 8 比特 (UTF-8) 或者16 比特 (UTF-16) 。UTF-8 額外的優(yōu)勢(shì)在于可以向后兼容 8 比特的 ASCII。這也使其超過(guò) ASCII成為網(wǎng)上最流行的編碼方式。Swift 將 UTF-16 和 UTF-8 的編碼單元分別用 UInt16 和 UInt8 來(lái)表示.
標(biāo)量在 Swift 字符串字面量中以 "\u{xxxx}" 來(lái)表示,其中的 xxxx 是十六進(jìn)制的數(shù)字。比如歐元符號(hào) € 在 Swift 中寫(xiě)作 "\u{20AC}"。Unicode 標(biāo)量在Swift 中對(duì)應(yīng)的類型是 Unicode.Scalar,它是一個(gè)對(duì) UInt32 的封裝類型.
總結(jié)來(lái)說(shuō):
用戶所認(rèn)為的在屏幕上顯示的 “單個(gè)字符” 可能仍需要由多個(gè)編碼點(diǎn)組合而成。在 Unicode 中,這種從用戶視?看到的字符有一個(gè)術(shù)語(yǔ),它叫做擴(kuò)展字位簇.
如何用標(biāo)量來(lái)形成字位簇的規(guī)則,將決定字符文本是如何分段的。比如說(shuō),讓你敲擊鍵盤(pán)上的退格鍵時(shí),你期望的是文本編輯器刪除掉一個(gè)字位簇。這個(gè) “字符” 有可能是由多個(gè) Unicode標(biāo)量組成的,每個(gè)標(biāo)量在文本表示的內(nèi)存存儲(chǔ)中,又可能使用了可變數(shù)量的編碼單元。在 Swift中,字位簇由 Character 類型進(jìn)行表示,這個(gè)類型可以對(duì)任意數(shù)量的標(biāo)量進(jìn)行編碼,并形成一個(gè)從用戶?度來(lái)看的字符.
1.字位簇
Unicode 將U+00E9 (帶尖音符的小寫(xiě)拉丁字? e) 定義成一個(gè)單一值。不過(guò)你也可以用一個(gè)普通的字? “e”后面跟一個(gè) U+0301 (組合尖音符) 來(lái)表達(dá)它。這兩種寫(xiě)法都顯示為 é,而且用戶多半也對(duì)兩個(gè)都顯示為 “résumé” 的字符串彼此相等且含有六個(gè)字符有著合理的預(yù)期,而不管里面的兩個(gè)“é” 是由哪種方式生成的。Unicode 規(guī)范將此稱作標(biāo)準(zhǔn)等價(jià).這種特性非常人性化.
let single = "Pok\u{00E9}mon" // Pokémon
let double = "Poke\u{0301}mon" // Pokémon
single.count // 7
double.count // 7
single == double // true
single.utf16.count // 7
double.utf16.count // 8
(single as NSString).isEqual(to: double)// false
按照這兩個(gè)字符串在swift中,既可以是相等,又可以是不等.
在很多其他語(yǔ)言中,含有顏文字的字符串也令人有些驚訝。很多顏文字是由無(wú)法存放在單個(gè)UTF-16 編碼單元的 Unicode 標(biāo)量來(lái)表示的,比如在 Java 或者 C# 里,會(huì)認(rèn)為 "??" 是兩個(gè)“字符” ?,但是swift可以正確的處理.
let oneEmoji = "??" // U+1F602
oneEmoji.count // 1
2.字符串和集合
String 是 Character 值的集合,并且遵循Collection協(xié)議,這點(diǎn)在Swift2,3,4里來(lái)回修改,主要是因?yàn)橛袝r(shí)候String不能很好的符合集合的特性,例如將兩個(gè)集合連接的時(shí)候,你可能會(huì)假設(shè)所得到的集合的?度是兩個(gè)用來(lái)連接的集合?度之和。但是對(duì)于字符串來(lái)說(shuō),如果第一個(gè)集合的末尾和第二個(gè)集合的開(kāi)頭能夠形成一個(gè)字位簇的話,它們就不再相等.
因此,String 并不是一個(gè)可以隨機(jī)訪問(wèn)的集合。就算知道給定字符串中第 n 個(gè)字符的位置,也并不會(huì)對(duì)計(jì)算這個(gè)字符之前有多少個(gè) Unicode 標(biāo)量有任何幫助。所以,String 只實(shí)現(xiàn)了 BidirectionalCollection。你可以從字符串的頭或者尾開(kāi)始,向后或者向前移動(dòng),代碼會(huì)察看毗鄰字符的組合,跳過(guò)正確的字節(jié)數(shù)。不管怎樣,你每次只能迭代一個(gè)字符.
String沒(méi)有實(shí)現(xiàn)MutableCollection,所以沒(méi)有set下標(biāo)方法,當(dāng)需要修改字符串的時(shí)候需要使用replaceSubrange
var greeting = "Hello, world!"
if let comma = greeting.firstIndex(of: ",") {
greeting.replaceSubrange(comma..., with: " again.")
}
greeting // Hello again.
3.字符串索引和子字符串**
String.Index 是 String 和它的視圖所使用的索引類型,它本質(zhì)上是一個(gè)存儲(chǔ)了從字符串開(kāi)頭的字節(jié)偏移量的不透明值.很多看起來(lái)很簡(jiǎn)單的任務(wù),比如說(shuō)要提取字符串的前四個(gè)字符,實(shí)現(xiàn)看起來(lái)都會(huì)有些奇怪.
不過(guò)可以使用Collection的接口
s[..<s.index(s.startIndex, offsetBy: 4)] // abcd
s.prefx(4) // abcd
for (i,c) in "Hello".enumerated(){
}
if let idx = "Hello!".firstIndex(of: "!"){
}
和所有集合類型一樣,String 有一個(gè)特定的 SubSequence 類型,它就是 Substring。Substring 和 ArraySlice 很相似:它是一個(gè)以不同起始和結(jié)束索引的對(duì)原字符串的切片。子字符串和原字符串共享文本存儲(chǔ),這帶來(lái)的巨大的好處,它讓對(duì)字符串切片成為了非常高效的操作.
在你對(duì)一個(gè) (可能會(huì)很?的) 字符串進(jìn)行迭代并提取它的各個(gè)部分的循環(huán)中,切片的高效特性就非常重要了。這類任務(wù)可能包括在文本中尋找某個(gè)單詞出現(xiàn)的所有位置,或者解析一個(gè) CSV 文件等。在這里,字符串分割是一個(gè)很有用的操作。Colleciton 定義了一個(gè) split 方法,它會(huì)返回一個(gè)子序列的數(shù)組 (也就是 [Substring])。最常用的一種形式是
extension Collection where Element: Equatable {
public func split(separator: Element, maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true) ->[SubSequence]
}
這個(gè)函數(shù)和 String 從 NSString 繼承來(lái)的 components(separatedBy:) 很類似,不過(guò)還多加了一個(gè)決定是否要丟棄空值的選項(xiàng).
4.StringProtocol
Substring 和 String 的接口幾乎完全一樣。這是通過(guò)一個(gè)叫做 StringProtocol 的通用協(xié)議來(lái)達(dá)到的,String 和 Substring 都遵守這個(gè)協(xié)議。因?yàn)閹缀跛械淖址?API 都被定義在StringProtocol 上,對(duì)于 Substring,你完全可以假裝將它看作就是一個(gè) String,并完成各項(xiàng)操作,直到需要傳遞到下一個(gè)系統(tǒng),或者需要保存起來(lái)的時(shí)候再初始化為Sting.
但是也因?yàn)槿绱?substring會(huì)持有原字符串,需要防止內(nèi)存泄漏,或者不必要的開(kāi)銷.
如果你想要擴(kuò)展 String 為其添加新的功能,將這個(gè)擴(kuò)展放在 StringProtocol 會(huì)是一個(gè)好主意,這可以保持 String 和 Substring API 的統(tǒng)一性.
但是需要注意的是,標(biāo)準(zhǔn)庫(kù)警告不要聲明任意新的遵守 StringProtocol 協(xié)議的類型。只有標(biāo)準(zhǔn)庫(kù)中的String 和Substring 類型是有效的適配類型。
5.CharacterSet
Foundation有一個(gè)CharacterSet,但是在swift面前它名不副實(shí),其實(shí)是 UnicodeScalar的Set,就是一個(gè)表示
一系列 Unicode 標(biāo)量的數(shù)據(jù)結(jié)構(gòu)體,完全和 Character 類型不兼容.
6.CustomStringConvertible 和CustomDebugStringConvertible
類似OC的description,可以給任意類型實(shí)現(xiàn)這兩個(gè)協(xié)議,用來(lái)格式化print輸出的內(nèi)容.
如果沒(méi)有實(shí)現(xiàn) CustomDebugStringConvertible,String(reīecting:) 會(huì)退回使用CustomStringConvertible。所以如果你的類型很簡(jiǎn)單,通常沒(méi)必要實(shí)現(xiàn)CustomDebugStringConvertible。不過(guò)如果你的自定義類型是一個(gè)容器,那么遵循CustomDebugStringConvertible 以打印其所含元素的調(diào)試描述信息會(huì)更考究一些.
extension AlertView:CustomStringConvertible, CustomDebugStringConvertible{
var debugDescription: String {
return ""
}
var description: String{
return ""
}
}