原地址http://segmentfault.net/a/1190000000444620
iOS開發(fā)之玩轉(zhuǎn)字符串
目錄結(jié)構(gòu)
在每個應(yīng)用里我們都大量使用字符串诚欠。下面我們將快速看看一些常見的操作字符串的方法捂掰,過一遍常見操作的最佳實(shí)踐根暑。
字符串的比較淫半、搜索和排序
排序和比較字符串比第一眼看上去要復(fù)雜得多樊诺。不只是因?yàn)樽址梢园韺Γ╯urrogate pairs )(詳見 Ole 寫的這篇關(guān)于 Unicode 的文章) 咬最,而且比較還與字符串的本地化相關(guān)邮辽。在某些極端情況下相當(dāng)棘手。
蘋果文檔中 String Programming Guide 里有一節(jié)叫做 “字符與字形集群(Characters and Grapheme Clusters)”港谊,里面提到一些陷阱骇吭。例如對于排序來說,一些歐洲語言將序列“ch”當(dāng)作單個字母歧寺。在一些語言里燥狰,“?”被認(rèn)為等同于 ‘a(chǎn)’ 棘脐,而在其它語言里它卻被排在 ‘z’ 后面。
而 NSString 有一些方法來幫助我們處理這種復(fù)雜性龙致。首先看下面的方法:
- (NSComparisonResult)compare:(NSString *)aString
options:(NSStringCompareOptions)mask
range:(NSRange)range
locale:(id)locale
它帶給我們充分的靈活性荆残。另外,還有很多“便捷函數(shù)”都使用了這個方法净当。
與比較有關(guān)的可用參數(shù)如下:
NSCaseInsensitiveSearch
NSLiteralSearch
NSNumericSearch
NSDiacriticInsensitiveSearch
NSWidthInsensitiveSearch
NSForcedOrderingSearch
它們都可以用邏輯或運(yùn)算組合在一起内斯。
NSCaseInsensitiveSearch :“A”等同于“a”,然而在某些地方還有更復(fù)雜的情況像啼。例如俘闯,在德國,“?” 和 “SS”是等價的忽冻。
NSLiteralSearch :Unicode 的點(diǎn)對 Unicode 點(diǎn)比較真朗。它只在所有字符都用相同的方式組成的情況下才會返回相等。LATIN CAPITAL LETTER A 加上 COMBINING RING ABOVE 并不等同于 LATIN CAPITAL LETTER A WITH RING ABOVE.
譯注:這個要解釋一下僧诚,首先遮婶,每一個Unicode都是有官方名字的!LATIN CAPITAL > LETTER A是一個大寫“A”湖笨,COMBINING RING ABOVE是一個? ?旗扑,LATIN CAPITAL > LETTER A WITH RING ABOVE,這是?前兩者的組合不等同于后者慈省。
NSNumericSearch:它對字符串里的數(shù)字排序臀防,所以 “Section 9” \< “Section 20” \< “Section 100.”
NSDiacriticInsensitiveSearch : “A” 等同于 “?” 等同于 “?.”
NSWidthInsensitiveSearch : 一些東亞文字(平假名 和 片假名)有全寬與半寬兩種形式。
很值得一提的是 - (NSComparisonResult)localizedStandardCompare: 边败,它排序的方式和 Finder 一樣袱衷。它對應(yīng)的選項(xiàng)是 NSCaseInsensitiveSearch 、 NSNumericSearch 笑窜、NSWidthInsensitiveSearch 以及 NSForcedOrderingSearch 致燥。如果我們要在UI上顯示一個文件列表,用它就最合適不過了排截。
大小寫不敏感的比較和音調(diào)符號不敏感的比較都是相對復(fù)雜和昂貴的操作嫌蚤。如果我們需要比較很多次字符串那這就會成為一個性能上的瓶頸(例如對一個大的數(shù)據(jù)集進(jìn)行排序),一個常見的解決方法是同時存儲原始字符串和折疊字符串匾寝。例如搬葬,我們的 Contact? 類有一個正常的 name? 屬性荷腊,在內(nèi)部它還有一個foldedName? 屬性艳悔,它將自動在 name變化時更新。那么我們就可以使用 NSLiteralSearch? 來比較 name? 的折疊版本女仰。 NSString? 有一個方法來創(chuàng)建折疊版本:
- (NSString *)stringByFoldingWithOptions:(NSStringCompareOptions)options
locale:(NSLocale *)locale
搜索
要在一個字符串中搜索子字符串猜年,最靈活性的方法是:
- (NSRange)rangeOfString:(NSString *)aString
options:(NSStringCompareOptions)mask
range:(NSRange)searchRange
locale:(NSLocale *)locale
同時抡锈,還有一些“便捷方法”,它們在最終都會調(diào)用上面這個方法乔外,我們可以傳入上面列出的參數(shù)床三,以及以下這些額外的參數(shù):
NSBackwardsSearch
NSAnchoredSearch
NSRegularExpressionSearch
NSBackwardsSearch :在字符串的末尾開始反向搜索。
NSAnchoredSearch : 只考慮搜索的起始點(diǎn)(單獨(dú)使用)或終止點(diǎn)(當(dāng)與 NSBackwardsSearch? 結(jié)合使用時)杨幼。這個方法可以用來檢查前綴或者后綴撇簿,以及大小寫不敏感(case-insensitive)或者音調(diào)不敏感(diacritic-insensitive)的比較。
NSRegularExpressionSearch :使用正則表達(dá)式搜索差购,要了解更多與使用正則表達(dá)式有關(guān)的信息四瘫,請關(guān)注 Chris’s 的 String Parsing 。
另外欲逃,還有一個方法:
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet
options:(NSStringCompareOptions)mask
range:(NSRange)aRange
與前面搜索字符串不同的是找蜜, 它只搜索給定字符集的第一個字符。即使只搜索一個字符稳析,但如果由于此字符是由元字符組成的序列(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 中的一個函數(shù) )類似, Objective C 中的 NSString 類也有如下的3個方法:
-initWithFormat:
-initWithFormat:arguments:
+stringWithFormat:
需要注意這些格式化方法都是 非本地化 的 。所以這些方法得到的字符串是不能直接拿來顯示在用戶界面上的陈惰。如果需要本地化咬清,那我們需要使用下面這些方法:
-initWithFormat:locale:
-initWithFormat:locale:arguments:
+localizedStringWithFormat:
Florian 有一篇關(guān)于 字符串的本地化 的文章更詳細(xì)地討論了這個問題。
printf(3)的man頁面有關(guān)于它如何格式化字符串的全部細(xì)節(jié)奴潘。除了所謂的轉(zhuǎn)換格式(它以%字符開始)旧烧,格式化字符串會被逐字復(fù)制:
double a = 25812.8074434;
float b = 376.730313461;
NSString *s = [NSString stringWithFormat:@"%g :: %g", a, b];
// "25812.8 :: 376.73"
我們格式化了兩個浮點(diǎn)數(shù)。注意單精度浮點(diǎn)數(shù)和雙精度浮點(diǎn)數(shù)共同了一個轉(zhuǎn)換格式画髓。
對象
除了來自 printf(3) 的轉(zhuǎn)換規(guī)范掘剪,我們還可以使用 %@? 來輸出一個對象。在對象描述那一節(jié)中有述奈虾,如果對象響應(yīng) -descriptionWithLocale:? 方法夺谁,則調(diào)用它,否則調(diào)用 -description 肉微。? %@? 被結(jié)果替換匾鸥。
整數(shù)
使用整形數(shù)字時,有些需要注意的細(xì)節(jié)碉纳。首先勿负,有符號數(shù)(d和i)和無符號數(shù)(o、u劳曹、x和X)分別有轉(zhuǎn)換規(guī)范奴愉。需要使用者選擇具體的類型琅摩。
如果我們使用的東西是 printf不知道的,我們必須要做類型轉(zhuǎn)換锭硼。 NSUInteger? 正是這樣一個例子房资,它在64位和32位平臺上是不一樣的。下面的例子可以同時工作在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? 相似但可同時在32位和64位平臺上正常工作颂郎。
浮點(diǎn)數(shù)
關(guān)于浮點(diǎn)數(shù)的轉(zhuǎn)換規(guī)則有8個: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ù)與一個$接在%后面陨献。這種寫法在進(jìn)行本地化的時候極其常見,因?yàn)樵诓煌Z言中懂更,各個參數(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()會輸出字符串沮协,并且它會加上時間戳龄捡、進(jìn)程名、進(jìn)程ID以及線程ID作為前綴慷暂。
實(shí)現(xiàn)能接受格式化字符串的方法
有時在我們自己的類中提供一個能接受格式化字符串的方法會很方便使用聘殖。假設(shè)我們要實(shí)現(xiàn)的是一個 To Do 應(yīng)用,它包含一個 Item 類。我們想要提供:
+ (instancetype)itemWithTitleFormat:(NSString *)format, ...
如此我們就可以使用:
Item *item = [Item itemWithFormat:@"Need to buy %@ for %@", food, pet];
這種類型的方法可以接受可變數(shù)量的參數(shù)就斤,所以被稱為可變參數(shù)方法悍募。我們必須使用一個定義在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 展開為一個方法 __attribute__洋魂,它會告訴編譯器在索引1處的參數(shù)是一個格式化字符串绷旗,而實(shí)際參數(shù)從索引2開始。這將允許編譯器檢查格式化字符串而且會像 NSLog() 和 -[NSString stringWithFormat:] 一樣輸出警告信息副砍。
字符與字符串組件
如有一個字符串 “bird” 衔肢,找出組成它的獨(dú)立字母是很簡單的。第二個字母是“i”(Unicode: LATIN SMALL LETTER I)豁翎。而對于像?se這樣的字符串就沒那么簡單了角骤。看起來像三個字母的組合可有多種方式心剥,例如:
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)。
如果我們要在字符層面處理一個字符串优烧,那我們就要小心翼翼蝉揍。蘋果官方文檔中 String Programming Guide 有一節(jié)叫做 “Characters and Grapheme Clusters”,里面有更多關(guān)于這一點(diǎn)的細(xì)節(jié)畦娄。
NSString有兩個方法:
-rangeOfComposedCharacterSequencesForRange:
-rangeOfComposedCharacterSequenceAtIndex:
上面這兩個方法在有的時候很有幫助又沾,例如,分開一個字符串時保證我們不會分開被稱為代理對(surrogate pairs)的東西熙卡。
如果我們要在字符串的字符上做工作杖刷, NSString 有個叫做 -enumerateSubstringsInRange:options:usingBlock: 的方法。
將 NSStringEnumerationByComposedCharacterSequences 作為選項(xiàng)傳遞驳癌,我們就能掃描所有的字符挺勿。例如,用下面的方法喂柒,我們可將字符串 “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 (letter != nil) {
[result appendString:letter];
};
}];
return result;
}
如文檔所示,詞和句的分界可能基于地區(qū)的變化而變化灾杰。因此有 NSStringEnumerationLocalized選項(xiàng)蚊丐。
多行文字字面量
編譯器的確有一個隱蔽的特性:把空格分隔開的字符串銜接到一起。這是什么意思呢艳吠?下面兩段代碼是完全等價的:
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";
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" @".";
譯者注:上面這行代碼原文是有誤的,原文是 NSString * @"The man " @"who knows everything " @"learns nothing" @"."? 凛篙,讀者可以嘗試一下黍匾,如果這樣寫是無法通過編譯的。
編譯器只是為我們提供了一個便捷的方式呛梆,將多個字符串在編譯期組合在了一起锐涯。
可變字符串
可變字符串有兩個常見的使用場景:(1)拼接字符串(2)替換部分字符串
創(chuàng)建字符串
可變字符串可以很輕易地把多個字符串在你需要的時候組合起來。
- (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)該是一個 NSString? 類型的對象纹腌,我們只是簡單地返回一個NSMutableString 類型的對象。
替換字符串
可變字符串除了追加組合之外滞磺,還提供了以下4個方法:
-deleteCharactersInRange:
-insertString:atIndex:
-replaceCharactersInRange:withString:
-replaceOccurrencesOfString:withString:options:range:
這些方法和 NSString 的類似:
-stringByReplacingOccurrencesOfString:withString:
-stringByReplacingOccurrencesOfString:withString:options:range:
-stringByReplacingCharactersInRange:withString:
但是它沒有創(chuàng)建新的字符串僅僅把當(dāng)前字符串變成了一個可變的類型升薯,這樣讓代碼更容易閱讀,以及提升些許性能击困。
NSMutableString *string; // 假設(shè)我們已經(jīng)有了一個名為 string 的字符串
// 現(xiàn)在要去掉它的一個前綴涎劈,做法如下:
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];
}
連接組件
一個看似微不足道但很常見的情況是字符串連接。比如現(xiàn)在有這樣幾個字符串:
Hildr
Heidrun
Gerd
Guerún
Freya
Nanna
Siv
Skaei
Gróa(chǎn)
我們想用它們來創(chuàng)建下面這樣的一個字符串:
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
那么在本地化的時候,如果是英語目派,應(yīng)該是:
"joiner.2components" = " and ";
"joiner.3components" = ", and ";
如果是德語坤候,則應(yīng)該是:
"joiner.2components" = " und ";
"joiner.3components" = " und ";
結(jié)合組件的逆過程可以用? -componentsSeparatedByString: ,這個方法會將一個字符串變成一個數(shù)組企蹭。例如白筹,將 “12|5|3” 變成 “12”、“5” 和 “3”谅摄。
對象描述
在許多面向?qū)ο缶幊陶Z言里徒河,對象有一個叫做 toString() 或類似的方法。在 Objective C 里送漠,這個方法是:
- (NSString *)description
以及它的兄弟方法:
- (NSString *)debugDescription
當(dāng)自定義模型對象時顽照,覆寫 -description 方法是一個好習(xí)慣,在UI上顯示該對象時調(diào)用的就是description方法的返回值闽寡。假定我們有一個 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)樵撟址怯脕碜鯱I顯示的爷狈,我們可能需要做本地化植影,那么我們就需要覆寫descriptionWithLocale:(NSLocale *)locale方法。
- (NSString *)descriptionWithLocale:(NSLocale *)locale;
%@ 會首先調(diào)用 -descriptionWithLocale涎永,如果沒有返回值思币,再調(diào)用 -description鹿响,在調(diào)試時,打印一個對象谷饿,我們用 po這個命令(它是print object的縮寫)
(lldb) po contact
如果在調(diào)試窗口的終端下輸入 po contact, 它會調(diào)用對象的 debugDescription方法惶我。默認(rèn)情況下debugDescription是直接調(diào)用 description。如果你希望輸出不同的信息博投,那么就分別覆寫兩個方法绸贡。大多數(shù)情況下,尤其是對于非數(shù)據(jù)模型的對象贬堵,你只需要覆寫 -description就能滿足需求了恃轩。
實(shí)際上對象的標(biāo)準(zhǔn)格式化輸出是這樣的:
- (NSString *)description;
{
return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
NSObject就是這么干的结洼。當(dāng)你覆寫該方法時黎做,也可以像這樣寫。假定我們有一個DetailViewController松忍,在它的UI上要顯示一個 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 我們并不希望改變我們的應(yīng)用程序的狀態(tài)宏所,因此我要確保檢查 isFault? 這個屬性。例如摊溶,我們可如下這樣實(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蛯ο螅剌d -description 簡單地返回描述實(shí)例的屬性名就可以了莫换。
文件路徑
簡單來說就是我們不應(yīng)該使用 NSString來描述文件路徑霞玄。對于 OS X 10.7 和 iOS 5, NSURL更便于使用拉岁,而且更有效率坷剧,它還能緩存文件系統(tǒng)的屬性。
再者喊暖, NSURL 有八個方法來訪問被稱為 resource values 的東西惫企。它們提供給我們一個穩(wěn)定的接口來獲取和設(shè)置文件與目錄的多種屬性,例如本地化文件名( NSURLLocalizedNameKey)陵叽、文件大心(NSURLFileSizeKey),以及創(chuàng)建日期( NSURLCreationDateKey)巩掺,等等偏序。
尤其是在遍歷目錄內(nèi)容時,使用 -[NSFileManagerenumeratorAtURL:includingPropertiesForKeys:options:errorHandler:] 附帶一個關(guān)鍵詞列表锌半,然后用 -getResourceValue:forKey:error: 檢索它們禽车,能帶來顯著的性能提升寇漫。
下面是一個簡短的例子展示了如何將它們組合在一起:
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)容時殉摔,這個方法能確保用非常高效的方式獲取它們州胳。在循環(huán)中,調(diào)用 -getResourceValue:… 能簡單地從 NSURL 得到已緩存的值逸月,而不用去訪問文件系統(tǒng)栓撞。
傳遞路徑到UNIX API
因?yàn)?Unicode 非常復(fù)雜,同一個字母有多種表示方式碗硬,所以我們需要很小心地傳遞路徑給UNIX API瓤湘。在這些情況里,一定不能使用 UTF8String 恩尾,正確地做法是使用 -fileSystemRepresentation 方法弛说,如下:
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 上翰意。如果我們不這么做木人,在打開一個文件名或路徑名包含合成字符的文件時我們將看到隨機(jī)錯誤。在 OS X 上冀偶,當(dāng)用戶的短名剛好包含合成字符時就會顯得特別糟糕醒第。
我們需要一個 char const * 版本的路徑的一些常見情況是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è)我們已經(jīng)有一個名為 path 的字符串
io = dispatch_io_create_with_path(DISPATCH_IO_STREAM,
path.fileSystemRepresentation,
O_RDONLY, 0, queue, cleanupHandler);
-fileSystemRepresentation 所做的是它首先將這個字符串轉(zhuǎn)換成文件系統(tǒng)的規(guī)范形式然后用UTF-8編碼。