Swift踩坑實戰之一個字符引發的Crash
最近因為一個字符引發瞭 Crash,因為實際的業務場景不便描述,這裡便用一段測試代碼作說明。
話不多說,直接上代碼:
let testCharacters: Set<Character> = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", "=", ">", "@", "[", "]", "`", "{", "}"] let testString = "@`Hello World`!" var result: UInt8 = 0 for character in testString { if testCharacters.contains(character) { result += character.asciiValue! } }
上面的代碼做的事情是:取出 testString 裡特定字符的 ASCII 碼,然後相加。
我們來 Review 下這段代碼,有經驗的同學應該立馬嗅到瞭代碼裡的壞味道:character.asciiValue! 這裡用瞭強解。
那這裡的強解用得合理嗎?因為定義在 testCharacters 裡的字符肯定都有對應的 ASCII 碼,咋一看這裡用強解也沒關系。
但是,如果我們實際跑一下,就會出現因為 asciiValue 為 nil 的強解 Crash 瞭。這是為什麼呢?
關鍵在於 testString 裡面包含瞭 全角字符。testString 裡的後一個 ` 是一個全角字符,它是沒有 asciiValue 的。
我們可以在 Swift Playgrounds 裡執行下面的代碼得到答案:
let halfWidth = "`" halfWidth.lengthOfBytes(using: .utf8) // 1 halfWidth.first!.isASCII // true halfWidth.first!.asciiValue // 96 let fullWidth = "`" fullWidth.lengthOfBytes(using: .utf8) // 3 fullWidth.first!.isASCII // false fullWidth.first!.asciiValue // nil // Character 實現 Equatable 協議,判斷出兩個值是相等的。 halfWidth == fullWidth // true
從上面代碼執行結果可以看到,halfWidth 這個半角字符占一個字節長度,對應的 ASCII 碼為 96 而全角字符 fullWidth 占三個字節長度,其 asciiValue 為空的。
Swift 數組的 contains 方法利用的是 Equatable 協議 , 從上面代碼裡 halfWidth == fullWidth 的結果為 true 來看,Character 實現的 Equatable 協議並沒有考慮字符全角/半角的情況。
用肉眼看,完全看不出字符有何不同,而 contains 方法結果為 true 也影響瞭我們的判斷,以為這個強解是 OK 的,稍不註意就導致瞭 Crash。
最後,從維基百科上整理瞭關於全角/半角的歷史知識:
在早期的計算機中,英語或拉丁字母語言使用的系統,每一個字母或符號,都是使用一字節的空間(一字節由 8 比特組成,共256個編碼空間)來儲存;而漢語、日語及韓語文字,由於數量大大超過256個,故慣常使用兩字節來儲存一個字符。所以這原本是編碼層面的“單字節”“雙字節”的問題。
當時的電腦使用等寬字體(如DOS、部分文字編輯器等)時,字體也就順應這種編碼形式,將中日韓文字的寬度繪制成拉丁字母和數字的兩倍,這樣字符的編碼存儲和顯示寬度可以一一對應起來:
- 單字節文字 顯示成 半寬,
- 雙字節文字 顯示成 全寬。
因此當時的用戶就開始習慣稱中、日、韓等文字為 全角字符,而稱拉丁字母或數字為 半角字符。
但是,後來計算機的文字編碼技術已經發生很大變化,存儲一個字符可能用一個、兩個、四個或者更多的字節。一個英文字符即使顯示為半寬,依照不同的編碼方式,並不一定是用一個字節存儲。
因此,現在字符編碼存儲和字符顯示寬度的已經沒有一一對應關系。
但是由於字符編碼和字形寬度曾經的對應關系,很多用戶一直習慣性地使用"全角/半角"詞匯。
因此現在的 全角字 可能是指:
- 用兩個字節存儲的字符
- ASCII(所謂半角英文和數字)以外所有的字符
- 顯示上字身寬度為一比一正方形的字形。
總結
到此這篇關於Swift踩坑實戰之一個字符引發Crash的文章就介紹到這瞭,更多相關Swift字符引發的Crash內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!