在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

玩轉(zhuǎn)字符串

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

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

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

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

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

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

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

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

NSCaseInsensitiveSearch
NSLiteralSearch
NSNumericSearch
NSDiacriticInsensitiveSearch
NSWidthInsensitiveSearch
NSForcedOrderingSearch

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

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

NSLiteralSearch:Unicode 的點對點比較。它只在所有字符都用相同的方式組成的情況下才會返回相等(即 NSOrderedSame)。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:一些東亞文字(平假名和片假名)有全寬與半寬兩種形式。

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

大小寫不敏感的比較和音調(diào)符號不敏感的比較都是相對復(fù)雜和昂貴的操作。如果我們需要比較很多次字符串那這就會成為一個性能上的瓶頸(例如對一個大的數(shù)據(jù)集進行排序),一個常見的解決方法是同時存儲原始字符串和折疊字符串。例如,我們的 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:只考慮搜索的起始點(單獨使用)或終止點(當(dāng)與 NSBackwardsSearch 結(jié)合使用時)。這個方法可以用來檢查前綴或者后綴,以及大小寫不敏感(case-insensitive)或者音調(diào)不敏感(diacritic-insensitive)的比較。

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

另外,還有一個方法:

- (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)于字符串的本地化的文章更詳細地討論了這個問題。

printf(3) 的 man 頁面有關(guān)于它如何格式化字符串的全部細節(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"

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

對象

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

整數(shù)

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

如果我們使用 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 具有一樣的功能,它們都打印出有符號十進制數(shù)。%o 就較為晦澀了:它使用八進制表示。%u 輸出無符號十進制數(shù)——它是我們常用的。最后 %x%X 使用十六進制表示——后者使用大寫字母。

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

我們可以傳入特定參數(shù),來設(shè)置最小字段寬度和最小數(shù)字位數(shù)(默認兩者都是 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 位平臺上正常工作。

浮點數(shù)

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

通常 %g 是浮點數(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ù)字數(shù)。

指定位置

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

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

我們只需將從 1 開始的參數(shù)與一個 $ 接在 % 后面。這種寫法在進行本地化的時候極其常見,因為在不同語言中,各個參數(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() 會輸出字符串,并且它會加上時間戳、進程名、進程 ID 以及線程 ID 作為前綴。

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

有時在我們自己的類中提供一個能接受格式化字符串的方法會很方便使用。假設(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ù)。上面方法的實現(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];
}

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

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

NS_FORMAT_FUNCTION 展開為一個方法 __attribute__,它會告訴編譯器在索引 1 處的參數(shù)是一個格式化字符串,而實際參數(shù)從索引 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 寫的這篇關(guān)于 Unicode 的文章 里可以讀到更多關(guān)于聯(lián)合標記(combining marks)的信息,其他語言文字有更多復(fù)雜的代理對(complicated surrogate pairs)。

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

NSString 有兩個方法:

-rangeOfComposedCharacterSequencesForRange:
-rangeOfComposedCharacterSequenceAtIndex:

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

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

-enumerateSubstringsInRange:options:usingBlock:

options 這里傳入 NSStringEnumerationByComposedCharacterSequences 這個參數(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 這個選項。

多行文字字面量

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

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

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

可變字符串

可變字符串有兩個常見的使用場景:(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;
}

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

替換子字符串

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

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

NSString 也有類似的方法:

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

但是 NSMutableString 的那些方法不會創(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:@", "];

如果我們將其顯示給用戶,我們就要使用本地化表達,確保將最后一部分替換相應(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)用的就是該方法的返回值。假定我們有一個 Contact 類,下面是它的 -description 方法的實現(xiàn):

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

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

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

因為該字符串是用來做 UI 顯示的,我們可能需要做本地化,那么我們就需要覆寫下面這個方法:

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

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

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

(lldb) po contact

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

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

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

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

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

NSManagedObject 子類的描述

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

再次,因為它們是模型對象,重載 -description 簡單地返回描述實例的屬性名就可以了。

文件路徑

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

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

尤其是在遍歷目錄內(nèi)容時,使用 -[NSFileManager enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:],并傳入一個關(guān)鍵詞(keys)列表,然后用 -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

因為 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 上。如果我們不這么做,在打開一個文件名或路徑名包含合成字符的文件時我們將看到隨機錯誤。在 OS X 上,當(dāng)用戶的短名剛好包含合成字符時就會顯得特別糟糕,比如 tómas。

有時我們可能需要路徑是一個不可變的常量,即 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)存在
io = dispatch_io_create_with_path(DISPATCH_IO_STREAM,
    path.fileSystemRepresentation,
    O_RDONLY, 0, queue, cleanupHandler);

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