Swift的字符串和OC有很大差別匪燕,下面詳細(xì)學(xué)習(xí)下留荔。
Grapheme Clusters (字形簇)
As you know, a string is made up of a collection of Unicode characters. Until now, you have considered one code point to precisely equal one character, and vice versa. However the term "character" is fairly loose.
就我們目前所知道的,字符串是由一連串的Unicode字符組成泻云。于此同時(shí)滓彰,我們也是通過一個(gè)代碼點(diǎn)來直接判斷兩個(gè)字符是否相等,反之亦然鸥印。但是勋功,"character"其實(shí)概念是相當(dāng)松散的坦报。
直入主題,在swift中我們表示cafe可以有兩種方式:
“ One example is the é in café, 其中é用unicode編碼單獨(dú)表示是233狂鞋,如果用combining character的形式就是e+聲調(diào)的形式片择,e是101,聲調(diào)是769骚揍,而后者這種方式就是Unicode標(biāo)準(zhǔn)中的字形簇字管。在swift中字形簇用Character來表示。
可能上面的解釋還不夠清楚解釋字形簇在swift的中意義信不,下面看個(gè)例子:
let cafeNormal = "café"
let cafeCombining = "cafe\u{0301}"
cafeNormal.count // 4
cafeCombining.count // 4
\u表示的是Unicode的速記法書寫形式嘲叔,我們可以用Unicode速記法書寫任意形式的Unicode編碼。這里為啥不直接寫呢抽活,因?yàn)殒I盤上敲不出來e加聲調(diào)的字符硫戈。
可以看到代碼二者的輸出長度一致,為什么呢下硕?因?yàn)樵赟wift中丁逝,string是被看做字形簇的一個(gè)集合汁胆,e加聲調(diào)就是一個(gè)字形簇,所以這里長度輸出都是4霜幼。
上面的結(jié)果意味著我們要花費(fèi)一個(gè)線性的時(shí)間去找出字符串的長度嫩码,因?yàn)槲覀円饌€(gè)遍歷字符串去找出有多少個(gè)字形簇。所以罪既,我們不能簡單的通過內(nèi)存中長度來判斷這個(gè)字符串有多大铸题。
挺麻煩的吧,所以最后swift給我們提供了unicodeScalars這種方式來訪問string的底層Unicode數(shù)據(jù):
cafeNormal.unicodeScalars.count // 4
cafeCombining.unicodeScalars.count // 5
for codePoint in cafeCombining.unicodeScalars {
print(codePoint.value)
}
輸出結(jié)果:
99
97
102
101
769
Indexing strings(字符串索引)
在Swift中琢感,我們無法通過下標(biāo)來直接訪問字符串的具體字符回挽,為啥呢?因?yàn)镾wift需要我們關(guān)注下string的底層原理猩谊,原理是啥千劈?就是我們上面說的字形簇。所以牌捷,字符串索引訪問的方式就比較蛋疼了墙牌,我們只能夠使用特殊的字符串索引類型而不是整形的下標(biāo)類型來訪問。
let firstIndex = cafeCombining.startIndex
這里firstIndex的數(shù)據(jù)類型就是String.Index類型暗甥。這里我們只是拿到了索引喜滨,具體訪問:
let firstChar = cafeCombining[firstIndex]
firstChar結(jié)果就是c。
下面撤防,我們嘗試訪問結(jié)尾的字符:
let lastIndex = cafeCombining.endIndex
let lastChar = cafeCombining[lastIndex]
但是虽风,錯(cuò)誤出現(xiàn)了
fatal error: Can't form a Character from an empty String
為啥呢?原因就是終結(jié)索引實(shí)際上是在字符串結(jié)尾的1寄月,(let cafeCombining = "cafe\u{0301}")辜膝。所以,我們需要這么做:
let lastIndex = cafeCombining.index(before: cafeCombining.endIndex)
let lastChar = cafeCombining[lastIndex]
也可以這么做:
let fourthIndex = cafeCombining.index(cafeCombining.startIndex, offsetBy: 3)
let fourthChar = cafeCombining[fourthIndex]
Equality with combining characters (如何判斷組合字符是否相等)
Swift采用一種叫canonicalization(規(guī)范化)的技術(shù)來處理象形字符之間的比較漾肮,和傳統(tǒng)的其他語言逐個(gè)字符比較厂抖,然后判斷是否都相等不同。Swift首先對字符串進(jìn)行規(guī)范化操作克懊,讓2個(gè)字符串變成同一種風(fēng)格忱辅,然后再逐個(gè)進(jìn)行比較,具體的實(shí)現(xiàn)方式我們不必關(guān)注谭溉,了解這個(gè)特性即可墙懂。
let equal = cafeNormal == cafeCombining
在Swift中,上面代碼的執(zhí)行結(jié)構(gòu)是true.
Strings as bi-directional collections(字符串作為雙向集合)
Swift做了一些內(nèi)存優(yōu)化扮念,雙向集合就是一種损搬,其實(shí)概念很簡單,看下代碼就知道了。
let name = "Matt"
let backwardsName = name.reversed()
backwardsName的類型是string嗎场躯?不是谈为,其實(shí)在swift中它是 reversed collection 類型,Swift做了內(nèi)存優(yōu)化踢关,他還是指向源字符串的內(nèi)存空間伞鲫。但是,使用起來和其他字符串并無差別签舞。
let secondCharIndex = backwardsName.index(backwardsName.startIndex, offsetBy: 1)
let secondChar = backwardsName[secondCharIndex] // "t”
如果你確實(shí)想要開辟一塊新空間秕脓,那么這么做:
let backwardsNameString = String(backwardsName)
Substrings (子串)
首先,我們來看一段代碼:
let fullName = "Matt Galloway"
let spaceIndex = fullName.index(of: " ")!
let firstName = fullName[fullName.startIndex..<spaceIndex] // "Matt”
- open-ended range (開放范圍)
Swift提供了一種新的范圍類型:開放范圍儒搭。我們只需要提供一個(gè)索引位置吠架,而另一個(gè)索引則被默認(rèn)假設(shè)為集合的開始或結(jié)尾,類似于數(shù)學(xué)中的開閉區(qū)間搂鲫。
應(yīng)用這個(gè)類型傍药,我們可以將上面的代碼修改如下:
let firstName = fullName[..<spaceIndex]
let lastName = fullName[fullName.index(after: spaceIndex)...]
細(xì)心點(diǎn),我們可以發(fā)現(xiàn)這時(shí)firstName的數(shù)據(jù)類型是Substring而不是string類型魂仍,這里其實(shí)和reversed string是一個(gè)原理拐辽,Swift本身是做了內(nèi)存優(yōu)化的,如果你想要一個(gè)全新string那么就這么做:
let lastNameString = String(lastName)
看到這里擦酌,我們不禁會想俱诸,為什么Swift的設(shè)計(jì)者要以這么復(fù)雜的方式來處理字符串呢?其實(shí)Substring在swift中是一個(gè)很巧妙的設(shè)計(jì)赊舶。Substring分享父string的內(nèi)存睁搭,這就意味著我們處理一個(gè)子串時(shí)不需要額外的內(nèi)存開銷。然后笼平,當(dāng)我們確實(shí)需要一個(gè)string類型的字符串時(shí)园骆,我們可以顯示的創(chuàng)建一個(gè)新的字符串類型,然后內(nèi)存中的數(shù)據(jù)就會拷貝到這個(gè)新字符串當(dāng)中出吹。
Swift的設(shè)計(jì)者本可以默認(rèn)的讓這種拷貝行為執(zhí)行遇伞,但是沒有這么做辙喂,設(shè)計(jì)者就是想讓你持有substring的類型捶牢,然后明確的讓你明白這底層到底發(fā)生了什么。如果你不是在函數(shù)返回值或者函數(shù)傳參的時(shí)候明確的遇到了要求數(shù)據(jù)類型為string時(shí)巍耗,你可能都不知道你持有的一直是substring類型的字符串秋麸,這時(shí),我們就可以用substring類型的字符串來顯示的初始化一個(gè)string類型的新字符串炬太。
Swift的設(shè)計(jì)者就是這么固執(zhí)灸蟆,但是,設(shè)計(jì)者這么嚴(yán)謹(jǐn)?shù)氖褂米址褪歉嬖V我們亲族,字符串使用很頻繁炒考,而且處理起來很復(fù)雜可缚,這點(diǎn)在日后編碼中是很重要的。