Swift 3.x 中 Strings/Characters 閑聊

unicode-latin-extended-additional.png

前言

本篇文章主要淺析字符串\字符在 Swift 和 Objective-C 之間的區(qū)別及其簡(jiǎn)單用法。如有不妥的地方還望大家及時(shí)幫忙糾正。

字符串判空

在 swift 語言中空字符串初始化方式常用的有兩種:

// 方式一:
let testEmptyString0 = ""

// 方式二:
let testEmptyString1 = String()

在開發(fā)過程中,我們應(yīng)該如何用正確的方式來對(duì)字符串進(jìn)行判空處理呢?


// 方式一:這種方式其實(shí)就是判斷 characters.count 是否為0
if testEmptyString0.isEmpty {
    // empty
}

// 方式二:
if testEmptyString0.characters.count {
    // empty
}

// 方式三:
if (testEmptyString0 as NSString).length {
    // empty
}

字符串長(zhǎng)度計(jì)算

Objective-C

首先我們來回憶一下,在 Objective-C 中字符串是怎么計(jì)算長(zhǎng)度的?我想大家都應(yīng)該知道。來看看蘋果是怎么說的:

A string object is implemented as an array of Unicode characters (in other words, a text string). An immutable string is a text string that is defined when it is created and subsequently cannot be changed. To create and manage an immutable string, use the NSString class. To construct and manage a string that can be changed after it has been created, use NSMutableString.

A string object presents itself as an array of Unicode characters. You can determine how many characters it contains with the length method and can retrieve a specific character with the characterAtIndex: method.

看完這段話,想必大家都明白 NSString 是怎么實(shí)現(xiàn)的,以及如何獲取其長(zhǎng)度。通過 length 方法即可,那么 length 方法是如何實(shí)現(xiàn)的呢?蘋果官方是這樣說的:length 方法利用的是 UTF-16 表示的十六位編碼單元數(shù)字為單位進(jìn)行計(jì)算的(The number of UTF-16 code units in the receiver.)。UTF-16是什么?(感興趣的童鞋可以看一下我之前寫的一篇文章,字符編碼(一)),此處不再詳述。

Swift 3.0

Unicode 標(biāo)量表示

在 Swift 中,字符和字符串都是基于 Unicode 標(biāo)量建立的,采用21位二進(jìn)制進(jìn)行編碼,共17個(gè)平面(除了基本多文種平面中的 UTF-16 代理對(duì)碼位外,即U+D800至U+DFFF的編碼空間),也就是說編碼范圍是U+0000-U+D7FFF 或者 U+E000-U+10FFFF。

A Unicode scalar is any Unicode code point in the range U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive. Unicode scalars do not include the Unicode surrogate pair code points, which are the code points in the range U+D800 to U+DFFF inclusive.”

因此在 Swift 中,我們可直接采用 Unicode 標(biāo)量的形式來表示字符或字符串,如:


let tingC = "\u{542C}" // 聽

let xinC = "\u{5FC3}" // 心
 

可擴(kuò)展的字形群集(簇)

在 Swift 中,每一個(gè) Character 類型實(shí)例都代表單個(gè)可擴(kuò)展的字形群集——即由一個(gè)或多個(gè) Unicode 標(biāo)量的序列組成的一個(gè)可讀字符。

漢字 “聽” 拼音為 tīng,以字母 ī 為例,用兩種方式表示。第一種,可以直接用單個(gè) Unicode 標(biāo)量 ī (LATIN SMALL LETTER I WITH MACRON) 來表示,即 U+012B,該字形群集中包含一個(gè) Unicode 標(biāo)量。第二種,可以采用兩個(gè) Unicode 標(biāo)量來表示,一個(gè)拉丁字母 i (LATIN SMALL LETTER I) 加上一個(gè)音調(diào)符(元音,COMBINING MACRON ACCENT)的標(biāo)量,即 U+0069 U+0304,這樣,當(dāng)字母 i 被 Unicode 文字渲染系統(tǒng)時(shí)就會(huì)轉(zhuǎn)換成 ī,該字形群集中包含兩個(gè) Unicode 標(biāo)量。


let tingO = "t" + "\u{0069}" + "ng" // Prints "ting "

let tingPS = "t" + "\u{0069}" + "\u{0304}" + "ng" // Prints "tīng"

let tingPD = "t" + "\u{012B}" + "ng" // Prints "tīng"

這兩種情況中,字母 ī 即代表了 Swift 中單個(gè) Character 類型實(shí)例,也代表了一個(gè)可擴(kuò)展的字形群集。想了解更多關(guān)于可擴(kuò)展的字形群集,可參考此鏈接。

字符串長(zhǎng)度

我們已經(jīng)簡(jiǎn)單了解了可擴(kuò)展的字形群集,現(xiàn)在我們?cè)賮砜纯?Swift 字符串中一些有意思的事。

Swift 中 String 類型,說白了就是 Character 類型實(shí)例的集合,在開發(fā)過程中,我們一般采用兩種方式來求字符串的長(zhǎng)度,第一種是轉(zhuǎn)成 Objective-C 中的 NSString 類型,通過 length 方法來獲取其長(zhǎng)度,第二種是通過字符串屬性 characters.count 的方式獲得。本小節(jié)主要討論第二種,本文會(huì)在結(jié)尾針對(duì)這兩種方式進(jìn)行比較。

在 Swift 中,細(xì)心的同學(xué)或許已經(jīng)發(fā)現(xiàn) tingPD 與 tingPS 字符串的字符數(shù)量是一樣的:


print("tingPD-Count:\(tingPD.characters.count), tingPS-Count:\(tingPS.characters.count)") 
// Prints "tingPD-Count:4, tingPS-Count:4"

下面我們來解決此疑惑,筆者已在前文說過,Swift 中 String\Character 都是基于 Unicode 標(biāo)量建立的,且 String 是 Character 的集合(即包含關(guān)系),而 String 屬性 characters.count 其實(shí)就是計(jì)算 Character 的數(shù)量,那么 character 是怎么定義的呢,或者說什么才算是一個(gè) character?此時(shí)又引出了一個(gè)概念——字形群集界限(Grapheme Cluster Boundaries),而”什么才算是一個(gè) character?“這個(gè)問題就是字形群集界限給出的答案,想深入了解的同學(xué)請(qǐng)看:傳送門。從用戶感觀(user-perceived)角度講,不管是字符 ī(U+012B) 或者是 i(U+0069) 再加上一個(gè)音調(diào)符(U+0304),這兩種表示最終的結(jié)果都是組成一個(gè)相同的可讀的字符,因此 tingPD 與 tingPS 字符串中的字符數(shù)量是一樣的。

通過上文的簡(jiǎn)單解釋,可以得出兩個(gè)結(jié)論:

  1. 一個(gè)字符串拼接一個(gè)字符時(shí),不一定會(huì)更改字符串的數(shù)量,即 characters.count 的值。

  2. 在沒有獲取到字形群集界限的時(shí)候,無法計(jì)算出該字符串的字符數(shù)量,因此必須遍歷字符串中全部的 Unicode 標(biāo)量以獲取字形群集界限,進(jìn)而確定字符串的字符數(shù)量。

下面在看一個(gè)例子,相信大家都已明白輸出結(jié)果的原因:


var iWord = "i"

print("iword-Count: \(iWord.characters.count)")
// Prints "iword-Count: 1"

iWord += "\u{0304}" // ī
print("iword-Count: \(iWord.characters.count)")
// Prints "iword-Count: 1"

.length 與 .characters.count 的區(qū)別

首先 .length 是 Objective-C 中字符串長(zhǎng)度計(jì)算方法,而 .characters.count 可以說是 Swift 中字符串長(zhǎng)度計(jì)算方法,由于 Swift 中 String 類型可以轉(zhuǎn)成 Objective-C 中的 NSString 類型,因此在 Swift 開發(fā)過程中可能有如下兩種寫法:


print("tingPS.characters.count")
// Prints "4"
print("(tingPS as NSString).length")
// Prints "5"

從上述結(jié)果可看出,.length 方法得到的字符串長(zhǎng)度為5,而 .characters.count 等于4,可能讀者會(huì)有點(diǎn)懵,同一個(gè)字符串怎么計(jì)算的長(zhǎng)度不一致?其實(shí) .length 與 .characters.count 的計(jì)算原理在上文已經(jīng)做了解釋,本小節(jié)就簡(jiǎn)單總結(jié)一下:

.length 與 .characters.count 返回值不總是相同的,.length 方法是采用 UTF-16 表示的編碼單元為單位進(jìn)行計(jì)算并返回的,即字母 i(U+0069) 、音調(diào)符(U+0304)會(huì)當(dāng)做兩個(gè)字符,因而長(zhǎng)度為2。.character.count 的值是通過字形群集界限來確定字符數(shù)量的,如還不理解請(qǐng)查看上文。(PS:其實(shí)這里也是 Swift 中采用索引的方式訪問字符串的原因)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容