幾乎所有的 APP 都會(huì)出現(xiàn)錯(cuò)誤。一些錯(cuò)誤可能會(huì)在你的可控范圍之外,例如硬盤空間耗盡或者網(wǎng)絡(luò)連接中斷。另一些錯(cuò)誤卻是可恢復(fù)的,例如無效用戶輸入。當(dāng)開發(fā)者在不斷追逐完美的過程中,也可能會(huì)伴隨著偶爾的編程錯(cuò)誤的出現(xiàn)。 如果你來自于其他的語言和開發(fā)平臺(tái),你也許會(huì)習(xí)慣于處理大多數(shù)錯(cuò)誤處理中的異常。當(dāng)你用 ObjC 編程的時(shí)候,異常僅會(huì)出現(xiàn)在編程錯(cuò)誤中,就像數(shù)組越界訪問或無效方法參數(shù)。這些就是在你的 APP 上線前的測試中,你需要排查并修復(fù)的問題。 NSError 類的實(shí)例代表了所有其他的錯(cuò)誤。這一章我們將簡單的介紹一下 NSError 對(duì)象的使用,包括怎樣處理構(gòu)造方法可能出現(xiàn)的失敗和返回錯(cuò)誤。更多信息參見?Error Handling Programming Guide
錯(cuò)誤是任何APP 生命周期中不可避免的一部分,假設(shè)你需要向一個(gè)遠(yuǎn)程網(wǎng)絡(luò)服務(wù)器請(qǐng)求數(shù)據(jù),在這個(gè)過程中有多種潛在問題可能出現(xiàn),包括:
遺憾的是,建立所有可能問題的應(yīng)急計(jì)劃與方案是不現(xiàn)實(shí)的。相反你必須為可能出現(xiàn)的錯(cuò)誤籌劃并且知道如何解決,從而獲得最好的用戶體驗(yàn)。
如果你正實(shí)現(xiàn)一個(gè)授權(quán)對(duì)象,它是與一個(gè)執(zhí)行某任務(wù)的構(gòu)造類( framework class )一起使用的,比如這個(gè)對(duì)象需要從遠(yuǎn)程服務(wù)器上下載信息。通常,你會(huì)發(fā)現(xiàn)你需要至少實(shí)現(xiàn)一個(gè)錯(cuò)誤關(guān)聯(lián)方法(error-related method)。例如 包括了一個(gè) connection:didFailWithError:? 方法的NSURLConnectionDelegate 協(xié)議:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
當(dāng)一個(gè)錯(cuò)誤發(fā)生時(shí),授權(quán)方法將會(huì)被調(diào)用,以向你提供一個(gè) NSError 對(duì)象來描述這個(gè)錯(cuò)誤。 一個(gè)NSError對(duì)象包含一個(gè)數(shù)字錯(cuò)誤代碼,域名和描述,以及封裝在一個(gè)用戶信息字典里的其他相關(guān)信息。 比起做出讓所有可能錯(cuò)誤都具有唯一的數(shù)字代碼的要求,Cocoa 和 Cocoa Touch 的錯(cuò)誤被劃分成域。例如一個(gè)錯(cuò)誤發(fā)生在?NSURLConnection 中,NSURLErrorDomain 域中的? connection:didFailWithError: 方法將會(huì)報(bào)一個(gè)錯(cuò)。錯(cuò)誤對(duì)象還包含一個(gè)本地化的描述,例如“找不到指定主機(jī)名的服務(wù)器”。
一些Cocoa 和 Cocoa Touch 的API 通過引用傳回錯(cuò)誤,舉個(gè)例子,你可能決定通過 ?NSData? 的 ?writeToURL:options:error: 方法,將你從網(wǎng)絡(luò)服務(wù)器獲得的信息通過存入硬盤的形式保存下來。這個(gè)方法的最后一個(gè)參數(shù)是一個(gè)指向 NSError 指針的引用。
- (BOOL)writeToURL:(NSURL *)aURL
options:(NSDataWritingOptions)mask
error:(NSError **)errorPtr;
在你調(diào)用這個(gè)方法之前,你需要?jiǎng)?chuàng)建一個(gè)合適的指針來傳遞地址:
NSError *anyError;
BOOL success = [receivedData writeToURL:someLocalFileURL
options:0
error:&anyError];
if (!success) {
NSLog(@"Write failed with error: %@", anyError);
// present error to user
}
當(dāng)錯(cuò)誤發(fā)生時(shí),writeToURL:options:error: 方法會(huì)返回 NO ,并會(huì)更新你的 anyError指針,指向一個(gè)錯(cuò)誤類來描述出現(xiàn)的問題。在處理應(yīng)用傳遞的錯(cuò)誤時(shí),重要的是去檢測方法的返回值以確定是否有錯(cuò)誤發(fā)生。不要只是測試是否有設(shè)置指向錯(cuò)誤的指針。
提示: 如果你并不關(guān)心出錯(cuò)的對(duì)象,則只需要將 NULL 傳遞給 error: 參數(shù)
對(duì)你的 APP 來說最好的用戶體驗(yàn)是隱蔽地從錯(cuò)誤中恢復(fù)。例如,你正在向遠(yuǎn)程網(wǎng)絡(luò)服務(wù)器發(fā)出請(qǐng)求,如果此時(shí)發(fā)生了錯(cuò)誤,你可以試著再向另一個(gè)服務(wù)器發(fā)送請(qǐng)求,或者你可以向用戶請(qǐng)求更多的信息,比如有效的用戶名和密碼,然后再次發(fā)出請(qǐng)求。 當(dāng)從錯(cuò)誤中恢復(fù)是不可能的時(shí)候,你才應(yīng)該去警告用戶。如果你正在使用 iOS的Cocoa Touch進(jìn)行開發(fā),你需要?jiǎng)?chuàng)建一個(gè) UIAlertView 并調(diào)節(jié)它,從而向用戶顯示錯(cuò)誤。如果你正在使用 OS X 的 Cocoa ,你可以在任何一個(gè) NSResponder 對(duì)象(像是一個(gè)界面、窗口甚至是應(yīng)用程序?qū)ο蟊旧恚┥险{(diào)用? presentError:? ,然后這個(gè)錯(cuò)誤便會(huì)被傳輸?shù)巾憫?yīng)鏈上,以進(jìn)行進(jìn)一步的調(diào)試或恢復(fù)。當(dāng)它到達(dá)應(yīng)用程序?qū)ο髸r(shí),應(yīng)用程序會(huì)通過一個(gè)警告板將錯(cuò)誤呈現(xiàn)給用戶。更多關(guān)于向用戶呈現(xiàn)錯(cuò)誤,參看?Displaying Information From Error Objects
為創(chuàng)建你自己的 NSError 類,你需要通過以下格式定義你自己的錯(cuò)誤類:
com.companyName.appOrFrameworkName.ErrorDomain
你需要為每一個(gè)可能發(fā)生在你的錯(cuò)誤域中的錯(cuò)誤選擇一個(gè)唯一的錯(cuò)誤編碼,同時(shí)還有合適的錯(cuò)誤描述,關(guān)于錯(cuò)誤的描述將會(huì)被存儲(chǔ)在用戶信息字典中。像下面這樣:
NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
NSString *desc = NSLocalizedString(@"Unable to…", @"");
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
NSError *error = [NSError errorWithDomain:domain
code:-101
userInfo:userInfo];
這個(gè)例子使用了 NSLocalizedDescriptionKey 函數(shù)來查看來自 ?Localizable.strings 文件中,錯(cuò)誤描述的本地化版本,即在本地化字符串資源中描述的那樣。
如果你需要像早前描述的那樣,通過引用將錯(cuò)誤傳回,你的方法標(biāo)識(shí)中還需要包含一個(gè)為指針設(shè)置的參數(shù),用來指向一個(gè) NSError 對(duì)象。你同時(shí)還需要利用返回的值指明是成功還是失敗,像是這樣:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr;
當(dāng)錯(cuò)誤發(fā)生時(shí),在返回表示失敗的NO之前,你需要首先檢查為錯(cuò)誤參數(shù)提供的指針值是否為空( NULL ),然后才能取消引用來設(shè)置錯(cuò)誤信息:
- (BOOL)doSomethingThatMayGenerateAnError:(NSError **)errorPtr {
...
// error occurred
if (errorPtr) {
*errorPtr = [NSError errorWithDomain:...
code:...
userInfo:...];
}
return NO;
}
ObjC 用與其他編程語言類似的方式支持異常,并且與這些語言,如 C++ 、Java ,支持的語法也很相似。就像 NSError 一樣,在 Cocoa 和 Cocoa Touch 里的異常,也是以 NSException 類實(shí)例為代表的對(duì)象。 如果你編寫的代碼可能導(dǎo)致異常拋出,你可以將這段代碼封裝在一個(gè) try-catch 塊內(nèi):
@try {
// do something that might throw an exception
}
@catch (NSException *exception) {
// deal with the exception
}
@finally {
// optional block of clean-up code
// executed whether or not an exception occurred
}
如果異常拋出發(fā)生在 @try 塊內(nèi),那么它將會(huì)被 @catch 塊捕捉,以方便你對(duì)它的處理。比如你在用使用異常作為錯(cuò)誤處理的低級(jí)別C++ 庫進(jìn)行開發(fā),你可能會(huì)捕捉到異常,并生成一個(gè)合適的 NSError 對(duì)象以向用戶顯示異常。
如果一個(gè)異常被拋出但并未被捕獲,那么默認(rèn)的未捕捉異常處理器(?uncaught exception handler )將會(huì)加載一個(gè)可以控制并終止應(yīng)用的消息。
你不應(yīng)該使用 try-block 塊來代替 ObjC 的標(biāo)準(zhǔn)程序檢測,以 NSArray 為例,你應(yīng)該總是先使用數(shù)組的數(shù)目( count )來確定元素的數(shù)量,然后才通過給定索引訪問一個(gè)對(duì)象。如果你發(fā)出了越界訪問請(qǐng)求,那么 objectAtIndex: 方法將會(huì)拋出一個(gè)異常,這樣你就可以在早期的開發(fā)過程中及時(shí)發(fā)現(xiàn) bug ——你應(yīng)該盡量避免在你已經(jīng)上線的 APP 中出現(xiàn)異常拋出。 更多關(guān)于 ObjC 應(yīng)用中的異常,參看?Exception Programming Topics
更多建議: