鍍金池/ 教程/ iOS/ 玩轉(zhuǎn)字符串
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

玩轉(zhuǎn)字符串

在每個(gè)應(yīng)用里我們都大量使用字符串。下面我們將快速看看一些常見的操作字符串的方法,過一遍常見操作的最佳實(shí)踐。

字符串的比較、搜索和排序

排序和比較字符串比第一眼看上去要復(fù)雜得多。不只是因?yàn)樽址梢园?strong>代理對(surrogate pairs )(詳見 Ole 寫的這篇關(guān)于 Unicode 的文章) ,而且比較還與字符串的本地化相關(guān)。在某些極端情況下相當(dāng)棘手。

蘋果文檔中 String Programming Guide 里有一節(jié)叫做 “字符與字形集群(Characters and Grapheme Clusters)”,里面提到一些陷阱。例如對于排序來說,一些歐洲語言將序列“ch”當(dāng)作單個(gè)字母。在一些語言里,“?”被認(rèn)為等同于 ‘a(chǎn)’ ,而在其它語言里它卻被排在 ‘z’ 后面。

NSString 有一些方法來幫助我們處理這種復(fù)雜性。首先看下面的方法:

- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(id)locale

它帶給我們充分的靈活性。另外,還有很多便捷函數(shù)(convenience functions)都使用了這個(gè)方法。

與比較有關(guān)的可用參數(shù)如下:

NSCaseInsensitiveSearch
NSLiteralSearch
NSNumericSearch
NSDiacriticInsensitiveSearch
NSWidthInsensitiveSearch
NSForcedOrderingSearch

它們都可以用邏輯“或”運(yùn)算符組合在一起。

NSCaseInsensitiveSearch:“A”等同于“a”,然而在某些地方還有更復(fù)雜的情況。例如,在德國,“?” 和 “SS”是等價(jià)的。

NSLiteralSearch:Unicode 的點(diǎn)對點(diǎn)比較。它只在所有字符都用相同的方式組成的情況下才會返回相等(即 NSOrderedSame)。LATIN CAPITAL LETTER A 加上 COMBINING RING ABOVE 并不等同于 LATIN CAPITAL LETTER A WITH RING ABOVE.

編者注 這里要解釋一下,首先,每一個(gè)Unicode都是有官方名字的!LATIN CAPITAL LETTER A是一個(gè)大寫“A”,COMBINING RING ABOVE是一個(gè) ?,LATIN CAPITAL LETTER A WITH RING ABOVE,這是?。前兩者的組合不等同于后者。

NSNumericSearch:它對字符串里的數(shù)字排序,所以 “Section 9” < “Section 20” < “Section 100.”

NSDiacriticInsensitiveSearch:“A” 等同于 “?” 等同于 “?.”

NSWidthInsensitiveSearch:一些東亞文字(平假名和片假名)有全寬與半寬兩種形式。

很值得一提的是-localizedStandardCompare:,它排序的方式和 Finder 一樣。它對應(yīng)的選項(xiàng)是 NSCaseInsensitiveSearch、NSNumericSearchNSWidthInsensitiveSearch 以及 NSForcedOrderingSearch。如果我們要在 UI 上顯示一個(gè)文件列表,用它就最合適不過了。

大小寫不敏感的比較和音調(diào)符號不敏感的比較都是相對復(fù)雜和昂貴的操作。如果我們需要比較很多次字符串那這就會成為一個(gè)性能上的瓶頸(例如對一個(gè)大的數(shù)據(jù)集進(jìn)行排序),一個(gè)常見的解決方法是同時(shí)存儲原始字符串和折疊字符串。例如,我們的 Contact 類有一個(gè)正常的 name 屬性,在內(nèi)部它還有一個(gè) foldedName 屬性,它將自動在 name 變化時(shí)更新。那么我們就可以使用 NSLiteralSearch 來比較 name 的折疊版本。 NSString 有一個(gè)方法來創(chuàng)建折疊版本:

- (NSString *)stringByFoldingWithOptions:(NSStringCompareOptions)options locale:(NSLocale *)locale

搜索

要在一個(gè)字符串中搜索子字符串,最靈活性的方法是:

- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)searchRange locale:(NSLocale *)locale

同時(shí),還有一些便捷方法,它們在最終都會調(diào)用上面這個(gè)方法,我們可以傳入上面列出的參數(shù),以及以下這些額外的參數(shù):

NSBackwardsSearch
NSAnchoredSearch
NSRegularExpressionSearch

NSBackwardsSearch:在字符串的末尾開始反向搜索。

NSAnchoredSearch:只考慮搜索的起始點(diǎn)(單獨(dú)使用)或終止點(diǎn)(當(dāng)與 NSBackwardsSearch 結(jié)合使用時(shí))。這個(gè)方法可以用來檢查前綴或者后綴,以及大小寫不敏感(case-insensitive)或者音調(diào)不敏感(diacritic-insensitive)的比較。

NSRegularExpressionSearch:使用正則表達(dá)式搜索,要了解更多與使用正則表達(dá)式有關(guān)的信息,請關(guān)注 Chris 寫的字符串解析這篇文章。

另外,還有一個(gè)方法:

- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet options:(NSStringCompareOptions)mask range:(NSRange)aRange

與前面搜索字符串不同的是,它只搜索給定字符集的第一個(gè)字符。即使只搜索一個(gè)字符,但如果由于此字符是由元字符組成的序列(composed character sequence),所以返回范圍的長度也可能大于1。

大寫與小寫

一定不要使用 NSString-uppercaseString 或者 -lowercaseString 的方法來處理 UI 顯示的字符串,而應(yīng)該使用 -uppercaseStringWithLocale 來代替, 比如:

NSString *name = @"Tómas";
cell.text = [name uppercaseStringWithLocale:[NSLocale currentLocale]];

格式化字符串

同 C 語言中的 sprintf 函數(shù)(ANSI C89 中的一個(gè)函數(shù))類似,Objective C 中的 NSString 類也有如下的 3 個(gè)方法:

-initWithFormat:
-initWithFormat:arguments:
+stringWithFormat:

需要注意這些格式化方法都是非本地化的。所以這些方法得到的字符串是不能直接拿來顯示在用戶界面上的。如果需要本地化,那我們需要使用下面這些方法:

-initWithFormat:locale:
-initWithFormat:locale:arguments:
+localizedStringWithFormat:

Florian 有一篇關(guān)于字符串的本地化的文章更詳細(xì)地討論了這個(gè)問題。

printf(3) 的 man 頁面有關(guān)于它如何格式化字符串的全部細(xì)節(jié)。除了以 % 字符開始的所謂格式轉(zhuǎn)換符(conversion specification),格式化字符串會被逐字復(fù)制:

double a = 25812.8074434;
float b = 376.730313461;
NSString *s = [NSString stringWithFormat:@"%g :: %g", a, b];
// "25812.8 :: 376.73"

我們格式化了兩個(gè)浮點(diǎn)數(shù)。注意單精度浮點(diǎn)數(shù) float 和雙精度浮點(diǎn)數(shù) double 共同了一個(gè)格式轉(zhuǎn)換符。

對象

除了來自 printf(3) 的轉(zhuǎn)換規(guī)范,我們還可以使用 %@ 來輸出一個(gè)對象。在對象描述那一節(jié)中有述,如果對象響應(yīng) -descriptionWithLocale: 方法,則調(diào)用它,否則調(diào)用 -description。%@ 被結(jié)果替換。

整數(shù)

使用整形數(shù)字時(shí),有些需要注意的細(xì)節(jié)。首先,有符號數(shù)(di)和無符號數(shù)(ou、xX)的格式轉(zhuǎn)換符是不一樣的,需要使用者根據(jù)具體情況來選擇。

如果我們使用 printf 支持的類型列表之外的類型,就必須要做類型轉(zhuǎn)換。NSUInteger 正是這樣一個(gè)例子,它在 64 位和 32 位平臺上是不一樣的。下面的例子可以同時(shí)工作在 32 位和 64 位平臺上:

uint64_t p = 2305843009213693951;
NSString *s = [NSString stringWithFormat:@"The ninth Mersenne prime is %llu", (unsigned long long) p];
// "The ninth Mersenne prime is 2305843009213693951"
Modifier d, i o, u, x, X
hh signed char unsigned char
h short unsigned short
(none) int unsigned int
l (ell) long unsigned long
ll (ell ell) long long unsigned long long
j intmax_t uintmax_t
t ptrdiff_t
z size_t

適用于整數(shù)的轉(zhuǎn)換規(guī)則有:

int m = -150004021;
uint n = 150004021U;
NSString *s = [NSString stringWithFormat:@"d:%d i:%i o:%o u:%u x:%x X:%X", m, m, n, n, n, n];
// "d:-150004021 i:-150004021 o:1074160465 u:150004021 x:8f0e135 X:8F0E135"

%d%i 具有一樣的功能,它們都打印出有符號十進(jìn)制數(shù)。%o 就較為晦澀了:它使用八進(jìn)制表示。%u 輸出無符號十進(jìn)制數(shù)——它是我們常用的。最后 %x%X 使用十六進(jìn)制表示——后者使用大寫字母。

對于 x%X%,我們可以在 0x 前面添加 # 前綴,增加可讀性。

我們可以傳入特定參數(shù),來設(shè)置最小字段寬度和最小數(shù)字位數(shù)(默認(rèn)兩者都是 0),以及左/右對齊。請查看 man 頁面獲取詳細(xì)信息。下面是一些例子:

int m = 42;
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// "[  42] [42  ] [ +42] [ 042] [0042]"
m = -42;
NSString *s = [NSString stringWithFormat:@"'%4d' '%-4d' '%+4d' '%4.3d' '%04d'", m, m, m, m, m];
// "[ -42] [-42 ] [ -42] [-042] [-042]"

%p 可用于打印出指針——它和 %#x 相似但可同時(shí)在 32 位和 64 位平臺上正常工作。

浮點(diǎn)數(shù)

浮點(diǎn)數(shù)的格式轉(zhuǎn)符有8個(gè):eEfFgGaA。但除了 %f%g 外我們很少使用其它的。對于指數(shù)部分,小寫的版本使用小寫 e,大寫的版本就使用大寫 E。

通常 %g 是浮點(diǎn)數(shù)的全能轉(zhuǎn)換符 ,它與 %f 的不同在下面的例子里顯示得很清楚:

double v[5] = {12345, 12, 0.12, 0.12345678901234, 0.0000012345678901234};
NSString *s = [NSString stringWithFormat:@"%g %g %g %g %g", v[0], v[1], v[2], v[3], v[4]];
// "12345 12 0.12 0.123457 1.23457e-06"
NSString *s = [NSString stringWithFormat:@"%f %f %f %f %f", v[0], v[1], v[2], v[3], v[4]];
// "12345.000000 12.000000 0.120000 0.123457 0.000001"

和整數(shù)一樣,我們依然可以指定最小字段寬度和最小數(shù)字?jǐn)?shù)。

指定位置

格式化字符串允許使用參數(shù)來改變順序:

[NSString stringWithFormat:@"%2$@ %1$@", @"1st", @"2nd"];
// "2nd 1st"

我們只需將從 1 開始的參數(shù)與一個(gè) $ 接在 % 后面。這種寫法在進(jìn)行本地化的時(shí)候極其常見,因?yàn)樵诓煌Z言中,各個(gè)參數(shù)所處的順序位置可能不盡相同。

NSLog()

NSLog() 函數(shù)與 +stringWithFormat: 的工作方式一樣。我們可以調(diào)用:

int magic = 42;
NSLog(@"The answer is %d", magic);

下面的代碼可以用同樣的方式構(gòu)造字符串:

int magic = 42;
NSString *output = [NSString stringWithFormat:@"The answer is %d", magic];

顯然 NSLog() 會輸出字符串,并且它會加上時(shí)間戳、進(jìn)程名、進(jìn)程 ID 以及線程 ID 作為前綴。

實(shí)現(xiàn)能接受格式化字符串的方法

有時(shí)在我們自己的類中提供一個(gè)能接受格式化字符串的方法會很方便使用。假設(shè)我們要實(shí)現(xiàn)的是一個(gè) To Do 類的應(yīng)用,它包含一個(gè) Item 類。我們想要提供:

+ (instancetype)itemWithTitleFormat:(NSString *)format, ...

如此我們就可以使用:

Item *item = [Item itemWithFormat:@"Need to buy %@ for %@", food, pet];

這種類型的方法接受可變數(shù)量的參數(shù),所以被稱為可變參數(shù)方法。我們必須使用一個(gè)定義在 stdarg.h 里的宏來使用可變參數(shù)。上面方法的實(shí)現(xiàn)代碼可能會像下面這樣:

+ (instancetype)itemWithTitleFormat:(NSString *)format, ...;
{
    va_list ap;
    va_start(ap, format);
    NSString *title = [[NSString alloc] initWithFormat:format locale:[NSLocale currentLocale] arguments:ap];
    va_end(ap);
    return [self itemWithTitle:title];
}

進(jìn)一步,我們要添加 NS_FORMAT_FUNCTION 到方法的定義里(即頭文件中),如下所示:

+ (instancetype)itemWithTitleFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

NS_FORMAT_FUNCTION 展開為一個(gè)方法 __attribute__,它會告訴編譯器在索引 1 處的參數(shù)是一個(gè)格式化字符串,而實(shí)際參數(shù)從索引 2 開始。這將允許編譯器檢查格式化字符串而且會像 NSLog()-[NSString stringWithFormat:] 一樣輸出警告信息。

字符與字符串組件

如有一個(gè)字符串 “bird” ,找出組成它的獨(dú)立字母是很簡單的。第二個(gè)字母是“i”(Unicode: LATIN SMALL LETTER I)。而對于像?se這樣的字符串就沒那么簡單了??雌饋硐袢齻€(gè)字母的組合可有多種方式,例如:

A    LATIN CAPITAL LETTER A
 ?    COMBINING RING ABOVE
s    LATIN SMALL LETTER S
e    LATIN SMALL LETTER E

或者

?    LATIN CAPITAL LETTER A WITH RING ABOVE
s    LATIN SMALL LETTER S
e    LATIN SMALL LETTER E

Ole 寫的這篇關(guān)于 Unicode 的文章 里可以讀到更多關(guān)于聯(lián)合標(biāo)記(combining marks)的信息,其他語言文字有更多復(fù)雜的代理對(complicated surrogate pairs)

如果我們要在字符層面處理一個(gè)字符串,那我們就要小心翼翼。蘋果的文檔中 String Programming Guide 里有一節(jié)叫做 “Characters and Grapheme Clusters”,里面有更多關(guān)于這一點(diǎn)的細(xì)節(jié)。

NSString 有兩個(gè)方法:

-rangeOfComposedCharacterSequencesForRange:
-rangeOfComposedCharacterSequenceAtIndex:

上面這兩個(gè)方法在有的時(shí)候很有幫助,例如,拆分一個(gè)字符串時(shí)保證我們不會把所謂的代理對(surrogate pairs)拆散。

如果我們要針對字符串中的字符做文章, NSString 提供了下面這個(gè)方法:

-enumerateSubstringsInRange:options:usingBlock:

options 這里傳入 NSStringEnumerationByComposedCharacterSequences 這個(gè)參數(shù),就可以掃描所有的字符。例如,用下面的方法,我們可將字符串 “International Business Machines” 變成 “IBM”:

- (NSString *)initials;
{
    NSMutableString *result = [NSMutableString string];
    [self enumerateSubstringsInRange:NSMakeRange(0, self.length) options:NSStringEnumerationByWords | NSStringEnumerationLocalized usingBlock:^(NSString *word, NSRange wordRange, NSRange enclosingWordRange, BOOL *stop1) {
        __block NSString *firstLetter = nil;
          [self enumerateSubstringsInRange:NSMakeRange(0, word.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *letter, NSRange letterRange, NSRange enclosingLetterRange, BOOL *stop2) {
              firstLetter = letter;
              *stop2 = YES;
          }];
          if (firstLetter != nil) {
              [result appendString:firstLetter];
        };
    }];
    return result;
}

如文檔所示,詞和句的分界可能基于地區(qū)的變化而變化。因此有 NSStringEnumerationLocalized 這個(gè)選項(xiàng)。

多行文字字面量

編譯器的確有一個(gè)隱蔽的特性:把空格分隔開的字符串銜接到一起。這是什么意思呢?這段代碼:

NSString *limerick = @"A lively young damsel named Menzies\n"
@"Inquired: ?Do you know what this thenzies??\n"
@"Her aunt, with a gasp,\n"
@"Replied: "It's a wasp,\n"
@"And you're holding the end where the stenzies.\n";

與下面這段代碼是完全等價(jià)的:

NSString *limerick = @"A lively young damsel named Menzies\nInquired: ?Do you know what this thenzies??\nHer aunt, with a gasp,\nReplied: "It's a wasp,\nAnd you're holding the end where the stenzies.\n";

前者看起來更舒服,但是有一點(diǎn)要注意:千萬不要在任意一行末尾加入逗號或者分號。

你也可以這樣做:

NSString * string = @"The man " @"who knows everything " @"learns nothing" @".";

編譯器只是為我們提供了一個(gè)便捷的方式,將多個(gè)字符串在編譯期組合在了一起。

可變字符串

可變字符串有兩個(gè)常見的使用場景:(1)拼接字符串(2)替換子字符串

拼接字符串

可變字符串可以很輕易地把多個(gè)字符串按照你的需要組合起來。

- (NSString *)magicToken
{
    NSMutableString *string = [NSMutableString string];
    if (usePrefix) {
        [string appendString:@">>>"];
    }
    [string appendFormat:@"%d--%d", self.foo, self.bar];
    if (useSuffix) {
        [string appendString:@">>>"];
    }
    return string;
}

這里要注意的是,雖然原本返回值應(yīng)該是一個(gè) NSString 類型的對象,我們在這里只是簡單地返回一個(gè) NSMutableString 類型的對象。

替換子字符串

除了追加組合之外,NSMutableString 還提供了以下4個(gè)方法:

-deleteCharactersInRange:
-insertString:atIndex:
-replaceCharactersInRange:withString:
-replaceOccurrencesOfString:withString:options:range:

NSString 也有類似的方法:

-stringByReplacingOccurrencesOfString:withString:
-stringByReplacingOccurrencesOfString:withString:options:range:
-stringByReplacingCharactersInRange:withString:

但是 NSMutableString 的那些方法不會創(chuàng)建新的字符串,而僅僅改變當(dāng)前字符串。這樣會讓代碼更容易閱讀,有時(shí)也會提升一些性能。

NSMutableString *string; // 假設(shè)我們已經(jīng)有了一個(gè)名為 string 的字符串
// 現(xiàn)在要去掉它的一個(gè)前綴,做法如下:
NSString *prefix = @"WeDon’tWantThisPrefix"
NSRange r = [string rangeOfString:prefix options:NSAnchoredSearch range:NSMakeRange(0, string.length) locale:nil];
if (r.location != NSNotFound) {
    [string deleteCharactersInRange:r];
}

連接組件

一個(gè)看似微不足道但很常見的情況是字符串連接。比如現(xiàn)在有這樣幾個(gè)字符串:

Hildr
Heidrun
Gerd
Guerún
Freya
Nanna
Siv
Skaei
Gróa(chǎn)

我們想用它們來創(chuàng)建下面這樣的一個(gè)字符串:

Hildr, Heidrun, Gerd, Guerún, Freya, Nanna, Siv, Skaei, Gróa(chǎn)

那么就可以這樣做:

NSArray *names = @["Hildr", @"Heidrun", @"Gerd", @"Guerún", @"Freya", @"Nanna", @"Siv", @"Skaei", @"Gróa(chǎn)"];
NSString *result = [names componentsJoinedByString:@", "];

如果我們將其顯示給用戶,我們就要使用本地化表達(dá),確保將最后一部分替換相應(yīng)語言的 “, and” :

@implementation NSArray (ObjcIO_GroupedComponents)

- (NSString *)groupedComponentsWithLocale:(NSLocale *)locale;
{
    if (self.count < 1) {
        return @"";
    } else if (self.count < 2) {
        return self[0];
    } else if (self.count < 3) {
        NSString *joiner = NSLocalizedString(@"joiner.2components", @"");
        return [NSString stringWithFormat:@"%@%@%@", self[0], joiner, self[1]];
    } else {
        NSString *joiner = [NSString stringWithFormat:@"%@ ", [locale objectForKey:NSLocaleGroupingSeparator]];
        NSArray *first = [self subarrayWithRange:NSMakeRange(0, self.count - 1)];
        NSMutableString *result = [NSMutableString stringWithString:[first componentsJoinedByString:joiner]];

        NSString *lastJoiner = NSLocalizedString(@"joiner.3components", @"");
        [result appendString:lastJoiner];
        [result appendString:self.lastObject];
        return result;
    }
}

@end

那么在本地化的時(shí)候,如果是英語,應(yīng)該是:

"joiner.2components" = " and ";
"joiner.3components" = ", and ";

如果是德語,則應(yīng)該是:

"joiner.2components" = " und ";
"joiner.3components" = " und ";

結(jié)合組件的逆過程可以用 -componentsSeparatedByString:,這個(gè)方法會將字符串變成一個(gè)數(shù)組。例如,將 “12|5|3” 變成 “12”、“5” 和 “3”。

對象描述

在許多面向?qū)ο缶幊陶Z言里,對象有一個(gè)叫做 toString() 或類似的方法。在 Objective C 里,這個(gè)方法是:

- (NSString *)description

以及它的兄弟方法:

- (NSString *)debugDescription

當(dāng)自定義模型對象時(shí),覆寫 -description 方法是一個(gè)好習(xí)慣,在 UI 上顯示該對象時(shí)調(diào)用的就是該方法的返回值。假定我們有一個(gè) Contact 類,下面是它的 -description 方法的實(shí)現(xiàn):

- (NSString *)description
{
    return self.name;
}

我們可以像下面代碼這樣格式化字符串:

label.text = [NSString stringWithFormat:NSLocalizedString(@"%@ has been added to the group “%@”.", @""), contact, group];

因?yàn)樵撟址怯脕碜?UI 顯示的,我們可能需要做本地化,那么我們就需要覆寫下面這個(gè)方法:

- (NSString *)descriptionWithLocale:(NSLocale *)locale;

格式轉(zhuǎn)換符 %@ 會首先調(diào)用 -descriptionWithLocale,如果沒有返回值,再調(diào)用 -description。

在調(diào)試時(shí),打印一個(gè)對象,我們用 po 這個(gè)命令(它是 print object 的縮寫):

(lldb) po contact

它會調(diào)用對象的 debugDescription 方法。默認(rèn)情況下 debugDescription 是直接調(diào)用 description。如果你希望輸出不同的信息,那么就分別覆寫兩個(gè)方法。大多數(shù)情況下,尤其是對于非數(shù)據(jù)模型的對象,你只需要覆寫 -description 就能滿足需求了。

實(shí)際上對象的標(biāo)準(zhǔn)格式化輸出是這樣的:

- (NSString *)description;
{
    return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}

NSObject 這個(gè)類內(nèi)部就是這么實(shí)現(xiàn)的。當(dāng)你覆寫該方法時(shí),也可以像這樣寫。假定我們有一個(gè) DetailViewController,在它的UI上要顯示一個(gè) contact,我們可能會這樣覆寫該方法:

- (NSString *)description;
{
    return [NSString stringWithFormat:@"<%@: %p> contact = %@", self.class, self, self.contact.debugDescription];
}

NSManagedObject 子類的描述

我們將特別注意向 NSManagedObject 的子類添加 -description/-debugDescription 的情況。由于 Core Data 的惰性加載機(jī)制(faulting mechanism)允許未加載數(shù)據(jù)的對象存在,所以當(dāng)我們調(diào)用 -debugDescription 時(shí)我們并不希望改變應(yīng)用程序的狀態(tài),因此需要檢查 isFault 這個(gè)屬性。例如,我們可如下這樣實(shí)現(xiàn)它:

- (NSString *)debugDescription;
{
    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: %p>", self.class, self];
    if (! self.isFault) {
        [description appendFormat:@" %@ \"%@\" %gL", self.identifier, self.name, self.metricVolume];
    }
    return description;
}

再次,因?yàn)樗鼈兪悄P蛯ο?,重載 -description 簡單地返回描述實(shí)例的屬性名就可以了。

文件路徑

簡單來說就是我們不應(yīng)該使用 NSString 來描述文件路徑。對于 OS X 10.7 和 iOS 5,NSURL 更便于使用,而且更有效率,它還能緩存文件系統(tǒng)的屬性。

再者,NSURL 有八個(gè)方法來訪問被稱為 resource values 的東西。這些方法提供了一個(gè)穩(wěn)定的接口,使我們可以用來獲取和設(shè)置文件與目錄的各種屬性,例如本地化文件名(NSURLLocalizedNameKey)、文件大?。?code>NSURLFileSizeKey),以及創(chuàng)建日期(NSURLCreationDateKey),等等。

尤其是在遍歷目錄內(nèi)容時(shí),使用 -[NSFileManager enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:],并傳入一個(gè)關(guān)鍵詞(keys)列表,然后用 -getResourceValue:forKey:error: 檢索它們,能帶來顯著的性能提升。

下面是一個(gè)簡短的例子展示了如何將它們組合在一起:

NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
NSURL *documents = [fm URLForDirectory:NSDocumentationDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:&error];
NSArray *properties = @[NSURLLocalizedNameKey, NSURLCreationDateKey];
NSDirectoryEnumerator *dirEnumerator = [fm enumeratorAtURL:documents
                                includingPropertiesForKeys:properties
                                                   options:0
                                              errorHandler:nil];
for (NSURL *fileURL in dirEnumerator) {
    NSString *name = nil;
    NSDate *creationDate = nil;
    if ([fileURL getResourceValue:&name forKey:NSURLLocalizedNameKey error:NULL] &&
        [fileURL getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL])
    {
        NSLog(@"'%@' was created at %@", name, creationDate);
    }
}

我們把屬性的鍵傳給 -enumeratorAtURL:... 方法中,在遍歷目錄內(nèi)容時(shí),這個(gè)方法能確保用非常高效的方式獲取它們。在循環(huán)中,調(diào)用 -getResourceValue:... 能簡單地從 NSURL 得到已緩存的值,而不用去訪問文件系統(tǒng)。

傳遞路徑到 UNIX API

因?yàn)?Unicode 非常復(fù)雜,同一個(gè)字母有多種表示方式,所以我們在傳遞路徑給 UNIX API 時(shí)需要非常小心。在這些情況里,一定不能使用 UTF8String,正確的做法是使用 -fileSystemRepresentation 這個(gè)方法,如下:

NSURL *documentURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:NULL];
documentURL = [documentURL URLByAppendingPathComponent:name];
int fd = open(documentURL.fileSystemRepresentation, O_RDONLY);

NSURL 類似,同樣的情況也發(fā)生在 NSString 上。如果我們不這么做,在打開一個(gè)文件名或路徑名包含合成字符的文件時(shí)我們將看到隨機(jī)錯(cuò)誤。在 OS X 上,當(dāng)用戶的短名剛好包含合成字符時(shí)就會顯得特別糟糕,比如 tómas。

有時(shí)我們可能需要路徑是一個(gè)不可變的常量,即 char const *,一個(gè)常見的例子就是 UNIX 的 open()close() 指令。但這種需求也可能發(fā)生在使用 GCD / libdispatch 的 I/O API 上。

dispatch_io_t
dispatch_io_create_with_path(dispatch_io_type_t type,
    const char *path, int oflag, mode_t mode,
    dispatch_queue_t queue,
    void (^cleanup_handler)(int error));

如果我們要使用 NSString 來做這件事,那我們要保證像下面這樣做:

NSString *path = ... // 假設(shè)這個(gè)字符串已經(jīng)存在
io = dispatch_io_create_with_path(DISPATCH_IO_STREAM,
    path.fileSystemRepresentation,
    O_RDONLY, 0, queue, cleanupHandler);

-fileSystemRepresentation 所做的是它首先將這個(gè)字符串轉(zhuǎn)換成文件系統(tǒng)的規(guī)范形式然后用 UTF-8 編碼。