錯(cuò)誤處理 - Dealing with Errors

2018-08-12 21:19 更新

錯(cuò)誤處理 - Dealing with Errors

幾乎所有的 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

對(duì)大多數(shù)的錯(cuò)誤使用 NSError

錯(cuò)誤是任何APP 生命周期中不可避免的一部分,假設(shè)你需要向一個(gè)遠(yuǎn)程網(wǎng)絡(luò)服務(wù)器請(qǐng)求數(shù)據(jù),在這個(gè)過程中有多種潛在問題可能出現(xiàn),包括:

  • 無網(wǎng)絡(luò)連接
  • 遠(yuǎn)程網(wǎng)絡(luò)服務(wù)器不可訪問
  • 遠(yuǎn)程網(wǎng)絡(luò)服務(wù)器不能提供你請(qǐng)求的信息
  • 得到的數(shù)據(jù)與你期望的不匹配
  • 遺憾的是,建立所有可能問題的應(yīng)急計(jì)劃與方案是不現(xiàn)實(shí)的。相反你必須為可能出現(xiàn)的錯(cuò)誤籌劃并且知道如何解決,從而獲得最好的用戶體驗(yàn)。

一些授權(quán)方法( delegate method )向你提醒錯(cuò)誤

如果你正實(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ù)器”。

一些方法可以通過引用傳遞錯(cuò)誤

一些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ù)

如果可以,盡量將錯(cuò)誤恢復(fù)或顯示給用戶

對(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

生成你自己的錯(cuò)誤信息

為創(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;
    }

用于編程錯(cuò)誤的異常

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

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)