iOS開發之玩轉字符串

在每一個應用裏咱們都大量使用字符串。下面咱們將快速看看一些常見的操做字符串的方法,過一遍常見操做的最佳實踐。html

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

排序和比較字符串比第一眼看上去要複雜得多。不僅是由於字符串能夠包含代理對(surrogate pairs )(詳見 Ole 寫的這篇關於 Unicode 的文章) ,並且比較還與字符串的本地化相關。在某些極端狀況下至關棘手。ios

蘋果文檔中 String Programming Guide 裏有一節叫作 「字符與字形集羣(Characters and Grapheme Clusters)」,裏面提到一些陷阱。例如對於排序來講,一些歐洲語言將序列「ch」看成單個字母。在一些語言裏,「ä」被認爲等同於 ‘a’ ,而在其它語言裏它卻被排在 ‘z’ 後面。正則表達式

而 NSString 有一些方法來幫助咱們處理這種複雜性。首先看下面的方法:編程

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

它帶給咱們充分的靈活性。另外,還有不少「便捷函數」都使用了這個方法。數組

與比較有關的可用參數以下:緩存

NSCaseInsensitiveSearch
NSLiteralSearch
NSNumericSearch
NSDiacriticInsensitiveSearch
NSWidthInsensitiveSearch
NSForcedOrderingSearch

它們均可以用邏輯或運算組合在一塊兒。app

NSCaseInsensitiveSearch :「A」等同於「a」,然而在某些地方還有更復雜的狀況。例如,在德國,「ß」 和 「SS」是等價的。編程語言

NSLiteralSearch :Unicode 的點對 Unicode 點比較。它只在全部字符都用相同的方式組成的狀況下才會返回相等。LATIN CAPITAL LETTER A 加上 COMBINING RING ABOVE 並不等同於 LATIN CAPITAL LETTER A WITH RING ABOVE.ide

譯註:這個要解釋一下,首先,每個Unicode都是有官方名字的!LATIN CAPITAL > LETTER A是一個大寫「A」,COMBINING RING ABOVE是一個  ̊,LATIN CAPITAL > LETTER A WITH RING ABOVE,這是Å前二者的組合不等同於後者。函數

NSNumericSearch:它對字符串裏的數字排序,因此 「Section 9」 \< 「Section 20」 \< 「Section 100.」

NSDiacriticInsensitiveSearch : 「A」 等同於 「Å」 等同於 「Ä.」

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

很值得一提的是 - (NSComparisonResult)localizedStandardCompare: ,它排序的方式和 Finder 同樣。它對應的選項是 NSCaseInsensitiveSearch 、 NSNumericSearch 、NSWidthInsensitiveSearch 以及 NSForcedOrderingSearch 。若是咱們要在UI上顯示一個文件列表,用它就最合適不過了。

大小寫不敏感的比較和音調符號不敏感的比較都是相對複雜和昂貴的操做。若是咱們須要比較不少次字符串那這就會成爲一個性能上的瓶頸(例如對一個大的數據集進行排序),一個常見的解決方法是同時存儲原始字符串和摺疊字符串。例如,咱們的 Contact  類有一個正常的 name  屬性,在內部它還有一個foldedName  屬性,它將自動在 name變化時更新。那麼咱們就可使用 NSLiteralSearch  來比較 name  的摺疊版本。 NSString  有一個方法來建立摺疊版本:

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

搜索

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

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

同時,還有一些「便捷方法」,它們在最終都會調用上面這個方法,咱們能夠傳入上面列出的參數,以及如下這些額外的參數:

NSBackwardsSearch
NSAnchoredSearch
NSRegularExpressionSearch

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

NSAnchoredSearch : 只考慮搜索的起始點(單獨使用)或終止點(當與 NSBackwardsSearch  結合使用時)。這個方法能夠用來檢查前綴或者後綴,以及大小寫不敏感(case-insensitive)或者音調不敏感(diacritic-insensitive)的比較。

NSRegularExpressionSearch :使用正則表達式搜索,要了解更多與使用正則表達式有關的信息,請關注 Chris’s 的 String Parsing 。

另外,還有一個方法:

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

與前面搜索字符串不一樣的是, 它只搜索給定字符集的第一個字符。即便只搜索一個字符,但若是因爲此字符是由元字符組成的序列(composed character sequence),因此返回範圍的長度也可能大於1。

大寫與小寫

必定不要使用 NSString  的 -uppercaseString  或者 -lowercaseString  的方法來處理 UI 顯示的字符串,而應該使用 -uppercaseStringWithLocale  來代替, 好比:

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

格式化字符串

同C語言中的 sprintf 函數( ANSI C89 中的一個函數 )相似, Objective C 中的 NSString 類也有以下的3個方法:

-initWithFormat:
-initWithFormat:arguments:
+stringWithFormat:

須要注意這些格式化方法都是 非本地化 的 。因此這些方法獲得的字符串是不能直接拿來顯示在用戶界面上的。若是須要本地化,那咱們須要使用下面這些方法:

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

Florian 有一篇關於 字符串的本地化 的文章更詳細地討論了這個問題。

printf(3)的man頁面有關於它如何格式化字符串的所有細節。除了所謂的轉換格式(它以%字符開始),格式化字符串會被逐字複製:

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

咱們格式化了兩個浮點數。注意單精度浮點數和雙精度浮點數共同了一個轉換格式。

對象

除了來自 printf(3) 的轉換規範,咱們還可使用 %@  來輸出一個對象。在對象描述那一節中有述,若是對象響應 -descriptionWithLocale:  方法,則調用它,不然調用 -description 。  %@  被結果替換。

整數

使用整形數字時,有些須要注意的細節。首先,有符號數(d和i)和無符號數(o、u、x和X)分別有轉換規範。須要使用者選擇具體的類型。

若是咱們使用的東西是 printf不知道的,咱們必需要作類型轉換。 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

適用於整數的轉換規則有:

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  具備同樣的功能,它們都打印出有符號十進制數。 %o  就較爲晦澀了:它使用八進制表示。 %u  輸出無符號十進制數——它是咱們經常使用的。最後 %x  和 %X  使用十六進制表示——後者使用大寫字母。

對於 x%  和 X%  ,咱們能夠在 0x 前面添加 「#」 井字符前綴看,增長可讀性。

咱們能夠傳入特定參數,來設置最小字段寬度和最小數字位數(默認二者都是0),以及左/右對齊。請查看man頁面獲取詳細信息。下面是一些例子:

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位平臺上正常工做。

浮點數

關於浮點數的轉換規則有8個:eEfFgGaA。但除了 %f 和 %g 外咱們不多使用其它的。對於指數部分,小寫的版本使用小寫 e,大寫的版本就使用大寫 E。

一般 %g  是浮點數的全能轉換符 ,它與 %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"

和整數同樣,咱們依然能夠指定最小字段寬度和最小數字數。

指定位置

格式化字符串容許使用參數來改變順序:

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

咱們只需將從1開始的參數與一個$接在%後面。這種寫法在進行本地化的時候極其常見,由於在不一樣語言中,各個參數所處的順序位置可能不盡相同。

NSLog()

NSLog() 函數與  +stringWithFormat: 的工做方式同樣。咱們能夠調用:

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

下面的代碼能夠用一樣的方式構造字符串:

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

顯然  NSLog()會輸出字符串,而且它會加上時間戳、進程名、進程ID以及線程ID做爲前綴。

實現能接受格式化字符串的方法

有時在咱們本身的類中提供一個能接受格式化字符串的方法會很方便使用。假設咱們要實現的是一個 To Do 應用,它包含一個 Item 類。咱們想要提供:

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

如此咱們就可使用:

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

這種類型的方法能夠接受可變數量的參數,因此被稱爲可變參數方法。咱們必須使用一個定義在stdarg.h裏的宏來使用可變參數。上面方法的實現代碼可能會像下面這樣:

+ (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];
}

進一步,咱們要添加 NS_FORMAT_FUNCTION 到方法的定義裏(在頭文件中),以下所示:

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

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

字符與字符串組件

若有一個字符串 「bird」 ,找出組成它的獨立字母是很簡單的。第二個字母是「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 寫的這篇關於 Unicode 的文章 裏能夠讀到更多關於聯合標記(combining marks)的信息,其餘語言文字有更多複雜的代理對(complicated surrogate pairs)

若是咱們要在字符層面處理一個字符串,那咱們就要當心翼翼。蘋果官方文檔中 String Programming Guide 有一節叫作 「Characters and Grapheme Clusters」,裏面有更多關於這一點的細節。
NSString有兩個方法:

-rangeOfComposedCharacterSequencesForRange:
-rangeOfComposedCharacterSequenceAtIndex:

上面這兩個方法在有的時候頗有幫助,例如,分開一個字符串時保證咱們不會分開被稱爲代理對(surrogate pairs)的東西。

若是咱們要在字符串的字符上作工做, NSString 有個叫作 -enumerateSubstringsInRange:options:usingBlock: 的方法。

將 NSStringEnumerationByComposedCharacterSequences 做爲選項傳遞,咱們就能掃描全部的字符。例如,用下面的方法,咱們可將字符串 「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;
}

如文檔所示,詞和句的分界可能基於地區的變化而變化。所以有 NSStringEnumerationLocalized選項。

多行文字字面量

編譯器的確有一個隱蔽的特性:把空格分隔開的字符串銜接到一塊兒。這是什麼意思呢?下面兩段代碼是徹底等價的:

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";

前者看起來更舒服,可是有一點要注意千萬不要在任意一行末尾加入逗號或者分號。

同時也能夠這樣作:

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

譯者注:上面這行代碼原文是有誤的,原文是 NSString * @"The man " @"who knows everything " @"learns nothing" @"."  ,讀者能夠嘗試一下,若是這樣寫是沒法經過編譯的。

編譯器只是爲咱們提供了一個便捷的方式,將多個字符串在編譯期組合在了一塊兒。

可變字符串

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

建立字符串

可變字符串能夠很輕易地把多個字符串在你須要的時候組合起來。

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

這裏要注意的是,雖然本來返回值應該是一個 NSString  類型的對象,咱們只是簡單地返回一個NSMutableString 類型的對象。

替換字符串

可變字符串除了追加組合以外,還提供瞭如下4個方法:

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

這些方法和 NSString 的相似:

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

可是它沒有建立新的字符串僅僅把當前字符串變成了一個可變的類型,這樣讓代碼更容易閱讀,以及提高些許性能。

NSMutableString *string; // 假設咱們已經有了一個名爲 string 的字符串
// 如今要去掉它的一個前綴,作法以下:
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];
}

 鏈接組件

一個看似微不足道但很常見的狀況是字符串鏈接。好比如今有這樣幾個字符串:

Hildr
Heidrun
Gerd
Guðrún
Freya
Nanna
Siv
Skaði
Gróa

咱們想用它們來建立下面這樣的一個字符串:

Hildr, Heidrun, Gerd, Guðrún, Freya, Nanna, Siv, Skaði, Gróa

那麼就能夠這樣作:

NSArray *names = @["Hildr", @"Heidrun", @"Gerd", @"Guðrún", @"Freya", @"Nanna", @"Siv", @"Skaði", @"Gróa"];
NSString *result = [names componentsJoinedByString:@", "];

若是咱們將其顯示給用戶,咱們就要使用本地化表達,確保將最後一部分替換相應語言的 , 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

那麼在本地化的時候,若是是英語,應該是:

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

若是是德語,則應該是:

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

結合組件的逆過程能夠用   -componentsSeparatedByString: ,這個方法會將一個字符串變成一個數組。例如,將 「12|5|3」 變成 「12」、「5」 和 「3」。

對象描述

在許多面向對象編程語言裏,對象有一個叫作 toString() 或相似的方法。在 Objective C 裏,這個方法是:

- (NSString *)description

以及它的兄弟方法:

- (NSString *)debugDescription

當自定義模型對象時,覆寫 -description 方法是一個好習慣,在UI上顯示該對象時調用的就是description方法的返回值。假定咱們有一個 Contact類,下面是它的 description方法實現。

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

咱們能夠像下面代碼這樣格式化字符串:

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

由於該字符串是用來作UI顯示的,咱們可能須要作本地化,那麼咱們就須要覆寫descriptionWithLocale:(NSLocale *)locale方法。

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

%@ 會首先調用 -descriptionWithLocale,若是沒有返回值,再調用 -description,在調試時,打印一個對象,咱們用 po這個命令(它是print object的縮寫)

(lldb) po contact

若是在調試窗口的終端下輸入 po contact, 它會調用對象的 debugDescription方法。默認狀況下debugDescription是直接調用 description。若是你但願輸出不一樣的信息,那麼就分別覆寫兩個方法。大多數狀況下,尤爲是對於非數據模型的對象,你只須要覆寫 -description就能知足需求了。

實際上對象的標準格式化輸出是這樣的:

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

NSObject就是這麼幹的。當你覆寫該方法時,也能夠像這樣寫。假定咱們有一個DetailViewController,在它的UI上要顯示一個 contact ,咱們可能會這樣覆寫該方法:

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

 NSManagedObject子類的描述

咱們將特別注意向 NSManagedObject 的子類添加 -description / -debugDescription 的狀況。因爲 Core Data的惰性加載機制(faulting mechanism)容許未加載數據的對象存在,因此當咱們調用 -debugDescription 咱們並不但願改變咱們的應用程序的狀態,所以我要確保檢查 isFault  這個屬性。例如,咱們可以下這樣實現它:

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

再次,由於它們是模型對象,重載 -description 簡單地返回描述實例的屬性名就能夠了。

文件路徑

簡單來講就是咱們不該該使用 NSString來描述文件路徑。對於 OS X 10.7 和 iOS 5, NSURL更便於使用,並且更有效率,它還能緩存文件系統的屬性。

再者, NSURL 有八個方法來訪問被稱爲 resource values 的東西。它們提供給咱們一個穩定的接口來獲取和設置文件與目錄的多種屬性,例如本地化文件名( NSURLLocalizedNameKey)、文件大小(NSURLFileSizeKey),以及建立日期( NSURLCreationDateKey),等等。

尤爲是在遍歷目錄內容時,使用 -[NSFileManagerenumeratorAtURL:includingPropertiesForKeys:options:errorHandler:] 附帶一個關鍵詞列表,而後用 -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: 方法中,在遍歷目錄內容時,這個方法能確保用很是高效的方式獲取它們。在循環中,調用 -getResourceValue:… 能簡單地從 NSURL 獲得已緩存的值,而不用去訪問文件系統。

傳遞路徑到UNIX API

由於 Unicode 很是複雜,同一個字母有多種表示方式,因此咱們須要很當心地傳遞路徑給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 相似,一樣的狀況也發生在 NSString 上。若是咱們不這麼作,在打開一個文件名或路徑名包含合成字符的文件時咱們將看到隨機錯誤。在 OS X 上,當用戶的短名恰好包含合成字符時就會顯得特別糟糕。

咱們須要一個 char const * 版本的路徑的一些常見狀況是UNIX open() 和 close() 指令。但這也可能發生在 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 = ... // 假設咱們已經有一個名爲 path 的字符串
io = dispatch_io_create_with_path(DISPATCH_IO_STREAM,
    path.fileSystemRepresentation,
    O_RDONLY, 0, queue, cleanupHandler);

-fileSystemRepresentation 所作的是它首先將這個字符串轉換成文件系統的規範形式而後用UTF-8編碼。


原文 Working with Strings
翻譯 iosinit
via 伯樂在線

相關文章
相關標籤/搜索