正如以前提早過的,基礎類庫(Foundation)擁有最好的、功能也最全的string類的實現。html
可是僅當程序員熟練掌握它時,一個string的實現纔是真的好。因此本週,咱們將瀏覽一些基礎類庫的string生態系統中常常用到且用錯的重要組成部分:NSCharacterSet
。git
若是你對什麼是字符編碼搞不清楚的話(即便你有很好的專業知識),那麼你應該抓住此次機會反覆閱讀Joel Spolsky的這篇經典的文章"The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)"。在頭腦中保持新鮮感將對你理解咱們將要探討的話題很是有幫助。程序員
NSCharacterSet
,以及它的可變版本NSMutableCharacterSet
,用面向對象的方式來表示一組Unicode字符。它常常與NSString
及NSScanner
組合起來使用,在不一樣的字符上作過濾、刪除或者分割操做。爲了給你提供這些字符是哪些字符的直觀印象,請看看NSCharacterSet
提供的類方法:正則表達式
alphanumericCharacterSet
objective-c
capitalizedLetterCharacterSet
express
controlCharacterSet
api
decimalDigitCharacterSet
數組
decomposableCharacterSet
oop
illegalCharacterSet
this
letterCharacterSet
lowercaseLetterCharacterSet
newlineCharacterSet
nonBaseCharacterSet
punctuationCharacterSet
symbolCharacterSet
uppercaseLetterCharacterSet
whitespaceAndNewlineCharacterSet
whitespaceCharacterSet
與它的名字所表述的相反,NSCharacterSet
跟 NSSet
一點關係都沒有。
雖然底層實現不太同樣,可是 NSCharacterSet
在概念上跟 NSIndexSet
還有點類似的。NSIndexSet
,以前提到過,表示一個有序的不重複的無符號整數的集合。Unicode字符跟無符號整數相似,大體對應一些拼寫表示。因此,一個 NSCharacterSet +lowercaseCharacterSet
字符集與一個包含97到122範圍的 NSIndexSet
是等價的。
如今咱們對理解 NSCharacterSet
的基本概念已經有了少量自信,讓咱們來看一些它的模式與反模式吧:
NSString -stringByTrimmingCharactersInSet:
是個你須要緊緊記住的方法。它常常會傳入 NSCharacterSet +whitespaceCharacterSet
或 +whitespaceAndNewlineCharacterSet
來刪除輸入字符串的頭尾的空白符號。
須要重點注意的是,這個方法 僅僅 去除了 開頭 和 結尾 的指定字符集中連續字符。這就是說,若是你想去除單詞之間的額外空格,請看下一步。
假設你去掉字符串兩端的多餘空格以後,還想去除單詞之間的多餘空格,這裏有個很是簡便的方法:
NSString *string = @"Lorem ipsum dolar sit amet."; string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSArray *components = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; components = [components filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self <> ''"]]; string = [components componentsJoinedByString:@" "];
首先,刪除字符串首尾的空格;而後用 NSString -componentsSeparatedByCharactersInSet:
在空格處將字符串分割成一個 NSArray
;再用一個 NSPredicate
去除空串;最後,用 NSArray -componentsJoinedByString:
用單個空格符將數組從新拼成字符串。注意:這種方法僅適用於英語這種用空格分割的語言。
如今看看反模式吧。請先看看 the answers to this question on StackOverflow。
在寫這篇文章的時候,排行第二的正確答案有 58 個頂和 2 個踩。排行第一的有 84 個頂和 24 個踩。
現在,排名第一的答案卻不是正確答案是不太正常的,可是這個問題已經破了不重複答案數(10個)的記錄,同時也破了不重複、徹底錯誤的答案數(9個)的記錄。
言歸正傳,這裏有 9 個錯誤答案:
"Use stringByTrimmingCharactersInSet
" - 正如你所知道的,它只去掉首尾的空格。
"Replace ' ' with ''" - 這個去除了全部的空格,勞而無功。
"Use a regular expression" - 有點用,但它沒有處理首尾的空格。用正則表達式有點大材小用了。
"Use Regexp Lite" - 說真的,正則表達式真心不必。同時爲了這點功能增長第三方庫很不值。
"Use OgreKit" - 同上,添加了第三方庫。
"Split the string into components, iterate over them to find components with non-zero length, and then re-combine" - 很接近了,可是 componentsSeparatedByCharactersInSet:
已經讓遍歷變得不必。
"Replace two-space strings with single-space strings in a while loop" - 錯誤且浪費計算資源。
"Manually iterate over each unichar
in the string and use NSCharacterSet -characterIsMember:
" - 用了一個複雜到讓人吃驚的程度的方法,卻忘了標準庫中已經有方法能夠用。
"Find and remove all of the tabs" - 有誰提到了製表符了?不過仍是謝謝了吧。
我我的並非想責怪回答問題的人——只是指出完成這個功能有多少種不一樣的方法,而這些方法有多少是徹底錯誤的。
不要用 NSCharacterSet
來分詞。 用 CFStringTokenizer
來替代它。
你用 componentsSeparatedByCharactersInSet:
來清理用戶輸入是能夠諒解的,可是用它來作更復雜的事情,你將陷入痛苦的深淵。
爲何?請記住,語言並非都用空格做爲詞的分界。雖然實際上以空格分界的語言使用很是普遍。但哪怕只算上中國和日本就已經有十多億人,佔了世界人口總量的 16%。
……即便是用空格分隔的語言,分詞也有一些模棱兩可的邊界條件,特別是複合詞匯和標點符號。
以上只爲說明:若是你想將字符串分紅有意義的單詞,那麼請用 CFStringTokenizer
(或者 enumerateSubstringsInRange:options:usingBlock:
)吧。
NSScanner
是個用以解析任意或半結構化的字符串的數據的類。當你爲一個字符串建立一個掃描器時,你能夠指定忽略哪些字符,這樣能夠避免那些字符以各類各樣的方式被包含到解析出來的結果中。
例如,你想從這樣一個字符串中解析出開門時間:
Mon-Thurs: 8:00 - 18:00 Fri: 7:00 - 17:00 Sat-Sun: 10:00 - 15:00
你會 enumerateLinesUsingBlock:
並像這樣用一個 NSScanner
來解析:
let skippedCharacters = NSMutableCharacterSet() skippedCharacters.formIntersectionWithCharacterSet(NSCharacterSet.punctuationCharacterSet()) skippedCharacters.formIntersectionWithCharacterSet(NSCharacterSet.whitespaceCharacterSet()) string.enumerateLines { (line, _) in let scanner = NSScanner(string: line) scanner.charactersToBeSkipped = skippedCharacters var startDay, endDay: NSString? var startHour: Int = 0 var startMinute: Int = 0 var endHour: Int = 0 var endMinute: Int = 0 scanner.scanCharactersFromSet(NSCharacterSet.letterCharacterSet(), intoString: &startDay) scanner.scanCharactersFromSet(NSCharacterSet.letterCharacterSet(), intoString: &endDay) scanner.scanInteger(&startHour) scanner.scanInteger(&startMinute) scanner.scanInteger(&endHour) scanner.scanInteger(&endMinute) }
咱們首先從空格字符集和標點符號字符集的並集構造了一個 NSMutableCharacterSet
。告訴 NSScanner
忽略這些字符以極大地減小解析這些字符的必要邏輯。
scanCharactersFromSet:
傳入字母字符集獲得每項中一星期內的開始和結束(可選)的天數。scanInteger
相似地,獲得下一個連續的整型值。
NSCharacterSet
和 NSScanner
讓你能夠快速而充滿自信地編碼。這二者真是完美組合。
NSCharacterSet
是基礎類庫中字符串處理系統中的一員,多是最容易被用錯或是誤解的一員。在腦中記住這些模式與反模式,你將不只能作一些頗有用的諸如管理空格及從字符串中讀信息之類的事情,更重要的是,你將避免誤入歧途。
若是「不出錯」對一個 NSHipster 來講不是最重要的事情,那我也不想成爲正確的了!