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