異常
異常(exception)就是指必須中斷程序的正常執(zhí)行來進(jìn)行處理的特殊狀態(tài)卢鹦。
編碼時(shí)采取將異常發(fā)生時(shí)的處理和程序原本應(yīng)該執(zhí)行的處理分開進(jìn)行的結(jié)構(gòu),就是異常處理機(jī)制(exception handling mechanism)蓬戚。當(dāng)異常狀況產(chǎn)生時(shí),程序會(huì)自動(dòng)脫離出普通的函數(shù)和方法的調(diào)用殿如,轉(zhuǎn)而執(zhí)行異常處理過程君仆。
Objective-C中的異常處理
利用異常的程序或通過引發(fā)異常來控制動(dòng)作的編程類型并不被推薦忙厌。也就是說凫岖,因設(shè)計(jì)錯(cuò)誤或程序錯(cuò)誤而產(chǎn)生的執(zhí)行時(shí)錯(cuò)誤,在開發(fā)時(shí)就應(yīng)該被去除逢净。
使用OC異常處理機(jī)制的目的:
對調(diào)試起到輔助作用
防止部分異常波及整個(gè)應(yīng)用
異常處理機(jī)制概述
異常句柄和異常處理域
異常發(fā)生時(shí)哥放,為執(zhí)行異常處理而準(zhǔn)備的流程稱為異常句柄(exception handler)。該異常句柄處理的對象的代碼部分稱為異常處理域(exception handling domain)爹土。
異常表示類NSException
通過生成NSException實(shí)例并發(fā)送raise消息甥雕,就可以啟動(dòng)異常處理。這也稱為“產(chǎn)生異痴鸵穑”或“拋出異成缏叮”。NSException實(shí)例稱為異常對象琼娘。
- (void)raise
//向異常對象拋出異常
當(dāng)向NSException類實(shí)例發(fā)送raise消息時(shí)該狀況已經(jīng)發(fā)生了峭弟。但是附鸽,在異常處理機(jī)制方面來看,只要沒有開始異常處理瞒瘸,就不是異常坷备。也就是說,向NSException實(shí)例發(fā)送消息raise這一時(shí)刻和異常發(fā)生被同等看待了情臭。而且有時(shí)也會(huì)將指向NSException的實(shí)例稱為異常省撑。
異常對象包含的信息:
信息
解釋
名稱
為了識別異常而使用的短字符串。判斷是否為應(yīng)該使用異常句柄處理的對象時(shí)使用俯在。使用下面的方法取出:
- (NSString *)name
原因用文字描述異常原因的長字符串竟秫。用如下方法取出:
- (NSString *)reason
用戶字典傳遞與異常相關(guān)的各種信息時(shí)使用的NSDictionary字典。字典中的信息因異常而異朝巫。用如下方法取出:
- (NSDictionary *)userInfo
異常的名字為全局字符串變量鸿摇,被預(yù)先定義在各種類的頭文件中。調(diào)查發(fā)生的異常時(shí)劈猿,將捕捉的異常對象名和這些異常名比較拙吉。而且,像什么樣的情況下發(fā)生什么異常的信息揪荣,在各類的參考文檔中都有詳述筷黔。
異常處理機(jī)制采用如下語法
@try{
/*異常處理域*/
/*在此記述正常處理*/
...
}
@catch(NSException *exception) {
/*異常句柄*/
/*記述異常處理過程。例如顯示錯(cuò)誤消息等*/
...
}
@finally{
/*后處理*/
/*在此記述無論異常發(fā)送與否最后都會(huì)執(zhí)行的代碼*/
...
}
@try必須寫在@catch和@fianlly的前面仗颈。@catch塊可以寫多個(gè)佛舱,也可以不寫,但此時(shí)必須有@finally塊挨决。
變量名exception只在@catch塊內(nèi)有效。
@finally塊不需要時(shí)也可以不寫肆捕。但只要寫了盖高,該塊內(nèi)的代碼一定會(huì)執(zhí)行慎陵。
簡單的異常處理的示例程序:
#import
intmain(intargc,char*argv[]) {
@autoreleasepool{
idarray = [NSMutableArrayarray];
@try{
if(argc ==1)
array = [arrayobjectAtIndex:1];//NSRangeException
else
[arrayaddObject:nil];//NSInvalidArgumentException
NSLog(@"success\n");
}
@catch(NSException *ex) {
NSString*name = [exname];
NSLog(@"Name: %@\n", name);
NSLog(@"Reason: %@\n", [exreason]);
if([nameisEqualToString:NSRangeException])
NSLog(@"Exception was caught successfully.\n");
else
[exraise];//再次拋出異常
}
NSLog(@"main exit");//行尾沒有換行符也沒有關(guān)系。
}
return0;
}
該程序根據(jù)命令行中是否存在參數(shù)而產(chǎn)生不同的異常席纽。生成空數(shù)組撞蚕,取出第一個(gè)不存在的元素時(shí)將產(chǎn)生異常NSRangeException润梯,追加新的元素nil時(shí)將產(chǎn)生異常NSInvalidArgumentException。因?yàn)锧catch塊中捕獲的異常可以訪問ex變量仆救,所以首先顯示異常的名字和原因。接著彤蔽,當(dāng)發(fā)生的異常為NSRangeException時(shí)就顯示能夠捕獲這一消息顿痪,而當(dāng)其他異常發(fā)生時(shí),則向ex發(fā)送raise消息來再次拋出異常蚁袭。這樣以來,NSRangeException以外的異常就會(huì)由運(yùn)行時(shí)系統(tǒng)來處理卖哎。
[ex raise]被執(zhí)行時(shí)删性,會(huì)顯示和普通的運(yùn)行時(shí)錯(cuò)誤發(fā)生時(shí)幾乎相同的消息蹬挺,然后程序異常就終止了。這是因?yàn)檫\(yùn)行時(shí)系統(tǒng)的未捕獲異常句柄(uncaught exception handler)流程捕獲并處理了異常巴帮。異常處理域外部發(fā)生異常時(shí)榕茧,或程序異常句柄中異常處理不能結(jié)束時(shí),未捕獲的異常句柄就會(huì)向終端肢簿,控制臺(tái)或日志文件中打印消息然后終止程序只恨。
但是抬虽,Cocoa應(yīng)用的主線程內(nèi)發(fā)生異常時(shí),NSApplication(UIApplication)實(shí)例將捕獲未處理的異常休涤,所以未捕獲異常句柄并不會(huì)直接終止應(yīng)用。然而序苏,即使應(yīng)用能夠捕捉到捷凄,異常的產(chǎn)生也仍可能會(huì)給應(yīng)用帶來若干影響,導(dǎo)致應(yīng)用之后的處理不能正確執(zhí)行匈睁。
異常的發(fā)生和傳播
異常的傳播
伴隨著程序的執(zhí)行桶错,某個(gè)異常處理域中可能還會(huì)有其他異常處理在執(zhí)行院刁,異常句柄中也可能再次發(fā)生其他異常。
只使用@catch塊不能結(jié)束某個(gè)異常處理時(shí)任岸,為了委托外部異常句柄來處理阅虫,有時(shí)就需要在@catch塊內(nèi)故意拋出異常。像這樣米碰,為了保證異常的正常處理购城,被調(diào)用方向調(diào)用方有序有序傳遞異常的過程,稱為異常傳播(propagation)吴趴。任何異常句柄侮攀,當(dāng)不能被合適處理或在異常處理域外部發(fā)生異常時(shí)兰英,最終都會(huì)由未捕獲異常句柄來處理。
自己觸發(fā)異常
如前所述陨闹,向異常對象發(fā)送raise消息就可以拋出異常,但是寨闹,實(shí)際上使用下面的類NSException的方法會(huì)更加容易君账。
+ (void)raise:(NSString *)name formar:(NSString *)format,...
//將異常名及原因保存成信息并創(chuàng)建臨時(shí)實(shí)例,直接產(chǎn)生異常帖蔓。
也可以自定義異常名并產(chǎn)生新型的異常瞳脓,此時(shí)劫侧,必須起一個(gè)可以和其他異常相區(qū)別的名字。
用@throw語法產(chǎn)生異常
將@throw方法應(yīng)用于NSException實(shí)例写妥,同發(fā)送raise消息是一樣的审姓。
@throwobject;
即便@catch塊內(nèi)沒有參數(shù)也可以使用@throw。此時(shí)扎筒,@catch塊捕捉的異常對象就被視為參數(shù)酬姆,同傳入?yún)?shù)的效果一樣辞色。
將NSException以外的普通對象指定為@throw參數(shù)也可以產(chǎn)生異常,@catch語法中必須要指明該對象的類型层亿。如果對象類型不明確立美,那么使用id類型后,結(jié)果就將捕捉包含NSException在內(nèi)的所有異常琳省。
@catch塊可以被書寫多個(gè)躲撰,但是必須要注意它們的順序拢蛋。這是因?yàn)椋鶕?jù)書寫順序能夠得知用@throw產(chǎn)生異常時(shí)使用的對象和哪個(gè)@catch塊參數(shù)一致快压。
@catch的特殊語法
@catch參數(shù)除了上面介紹的書寫方法外垃瞧,也可以采用下面的方式書寫。即括號內(nèi)不省略脉幢,而是寫三個(gè)點(diǎn)號嗦锐。
@catch(...) {
/*異常句柄*/
}
在需要捕捉異常而不需要知道異常內(nèi)容的情況下奕污,經(jīng)常會(huì)使用該寫法。如果存在其他的@catch塊贾陷,則必須將其書寫為最后的塊嘱根。塊內(nèi)@throw不指定參數(shù)時(shí)儿子,就能傳播異常。
異常傳播和@finally
@catch塊內(nèi)發(fā)生異常時(shí)(或傳播)蒋譬,或者不存在可以捕捉發(fā)生的異常的@catch塊時(shí)愉适,通常就會(huì)將處理移交給外部的異常句柄來執(zhí)行。但如果有@finally塊剂买,那么在向外側(cè)異常句柄移交處理前瞬哼,@finally塊的內(nèi)容先被執(zhí)行。在沒發(fā)生異常時(shí)坐慰,@finally會(huì)在@try塊之后執(zhí)行结胀,而當(dāng)異常不向外側(cè)傳播時(shí),@finally塊必須緊挨著@catch塊執(zhí)行攀操。所以秸抚,這里就要書寫實(shí)例釋放或文件關(guān)閉等必須要執(zhí)行的后處理耸别。如下圖。如果不捕捉異常而只想執(zhí)行后處理慈迈,也可以不寫@catch省有。
異常處理程序的注意點(diǎn)
遞歸調(diào)用(函數(shù)調(diào)用)中發(fā)生異常時(shí)蠢沿,處理有時(shí)就會(huì)跳過內(nèi)側(cè)的方法而轉(zhuǎn)移到外側(cè)的異常句柄中進(jìn)行。如圖恤磷。如果處理被中斷時(shí)務(wù)必進(jìn)行后處理野宜。
引用計(jì)數(shù)管理和異常匈子。不能保證對象被正確釋放。因此游岳,在OC中,寫代碼時(shí)不能以使用異常處理機(jī)制為前提喷户。
關(guān)于C語言中的全局跳轉(zhuǎn)函數(shù)晌区。C語言有兩個(gè)函數(shù)setjmp()和longjmp()朗若,它們都可以從函數(shù)調(diào)用的深層遞歸中返回昌罩。但是,異常處理機(jī)制在實(shí)現(xiàn)時(shí)使用了這些功能遣总,所以異常處理機(jī)制和這些跳轉(zhuǎn)函數(shù)不可以同時(shí)使用轨功。
斷言
斷言是什么
在程序中書寫程序必須滿足的條件古涧,當(dāng)條件被破壞時(shí)就觸發(fā)異常的結(jié)構(gòu)稱為斷言(assertion)。
斷言使用宏來書寫菇爪,定義在NSException.h中凳宙。
斷言宏的參數(shù)中寫著必須為真的條件职祷。
NASSert(x > y, *@"illegal values x(%d) & y(%d)", x, y);
條件為假時(shí),異常NSInternalInconsistencyException就會(huì)被觸發(fā)削葱。
編譯時(shí)定義了NS_BLOCK_ASSERTIONS宏時(shí)析砸,斷言不會(huì)被寫入代碼爆袍,而因?yàn)闆]有寫入作郭,就會(huì)被當(dāng)成空字符串處理夹攒。于是在編譯完成后的程序時(shí)胁塞,就要在編譯器的選項(xiàng)中加入-DNS_BLOCK_ASSERTIONS啸罢。選項(xiàng)-D的作用是可以從命令行定義宏,就不需要特意在文件中書寫#define了允懂。
斷言宏
斷言宏可以在OC方法內(nèi)以及C函數(shù)內(nèi)使用衩匣。斷言條件為假時(shí)琅捏,如果是在函數(shù)內(nèi),那么就用函數(shù)名來表示置侍;而如果是在方法內(nèi)拦焚,那么就用方法名來表示赎败。
首先,只在條件為真時(shí)使用的宏有兩個(gè):
NSParameterAssert(condition)….在方法內(nèi)使用
NSCParameterAssert(condition)…在函數(shù)內(nèi)使用
此外還有宏可以在條件為假時(shí)傳遞觸發(fā)的異常据忘,以及生成用來說明異常的字符串勇吊。它們和printf()一樣窍仰,取得格式字符串和對應(yīng)的參數(shù)。使用目前為止的C語言規(guī)范尚不可以定義參數(shù)個(gè)數(shù)不確定的宏针史,而在新的規(guī)范中是可以的啄枕。
在方法內(nèi)使用
NSAssert(condition,NSString *description [,arg,…])
在函數(shù)內(nèi)使用
NSCAssert(condition,Nsstring *description[,arg,…])
錯(cuò)誤處理
錯(cuò)誤處理結(jié)構(gòu)的目的
在Cocoa環(huán)境下,為了能夠統(tǒng)一表示錯(cuò)誤的種類和消息泌参,可以使用NSError沽一。簡單來說窟绷,NSError持有錯(cuò)誤說明和錯(cuò)誤的處理方法的相關(guān)信息兼蜈。
表示錯(cuò)誤的類NSError的使用方法
信息
解釋
域
導(dǎo)致錯(cuò)誤的技術(shù)因素拙友。例如,顯示Cocoa辐棒,Carbon漾根,Unix等中的誰的API相關(guān)的字符串鲫竞。用下面方法取出:
- (NSString *) domain
代碼表示錯(cuò)誤類型的整數(shù)值从绘。需要注意的是代碼的意義可根據(jù)域而發(fā)生變化。用下面的方法取出:
- (NSInteger)code
用戶字典為得到有關(guān)錯(cuò)誤的各種信息而使用的字典對象陕截。字典中保存的信息隨錯(cuò)誤而不同农曲。用下面的方法取出:
- (NSDictionary *)userInfo
如下所示驻债,準(zhǔn)備好保存的實(shí)例變量,并使用&運(yùn)算符指向該地址即可驯妄。發(fā)生指定的文件不存在等錯(cuò)誤時(shí)青扔,代入錯(cuò)誤對象給變量err。如果沒有發(fā)生錯(cuò)誤則不帶入對象谈息。因此為了明確表示錯(cuò)誤沒有發(fā)生侠仇,應(yīng)該養(yǎng)成預(yù)先為變量err代入nil的操作習(xí)慣犁珠。然而使用ARC管理內(nèi)存時(shí)犁享,方法內(nèi)的自動(dòng)變量地址必須將(NSError**)類型的參數(shù)指定為nil。
NSString*path, *contents;
NSError*err;
...
err =nil;
contents = [NSString stringWithContentsOfFile:path encoding:NSShiftJISStringEncoding error:&err];
獲取錯(cuò)誤對象的信息
為了獲得表示錯(cuò)誤內(nèi)容的消息字符串桨吊,可向NSError實(shí)例發(fā)送下面的消息视乐。沒有必要直接訪問用戶字典或域敢茁。
- (NSString *)localizedDescription
//返回說明錯(cuò)誤的字符串卷要。字符串被本地化,不會(huì)返回nil奕枝。用戶字典中如果存在NSLocalizedDescriptionKey鍵的對象隘道,則返回該對象。
- (NSString *)localizedFailureReason
//顯示錯(cuò)誤產(chǎn)生的原因忘晤,返回本地化的短字符串设塔。有時(shí)返回nil远舅。用戶字典中如果存在NSLocalizedFailureReasonErrorKey鍵的對象图柏,則返回該對象。
- (NSString *)localizedRecoverySuggestion
//顯示錯(cuò)誤的處理方法或建議例诀,返回本地化的短字符串裁着。有時(shí)返回nil繁涂。用戶字典中如果存在NSLocalizedRecoverySuggestionErrorKey鍵的對象爆土,則返回該對象诸蚕。
生成自定義錯(cuò)誤對象
與異常相同背犯,自己也可以生成NSError實(shí)例≈迅В可以使用下面的簡易常數(shù)類妄均。
+ (id)errorWithDomain:(NSString *)domain code:(NSInteger)code userInfo:(nullableNSDictionary *)dict
//指定域名丰包,錯(cuò)誤代碼和用戶字典后創(chuàng)建錯(cuò)誤對象。用戶字典為nil也沒有關(guān)系邑彪,域名必須要指定。典型的錯(cuò)誤域名如下所示矩动。
域名
解釋
頭文件
NSCocoaErrorDomain與Cocoa環(huán)境相關(guān)的錯(cuò)誤Foundation/FoundationErrors.h
AppKit/AppKitErrors.h
CoreData/CoreDataErrors.h
NSPOSIXErrorDomain
與Unix環(huán)境相關(guān)的錯(cuò)誤sys/errno.h
NSMachErrorDomain
與Mach核相關(guān)的錯(cuò)誤mach/kern_return.h
返回新創(chuàng)建的錯(cuò)誤對象時(shí)释漆,如果錯(cuò)誤原因和上述任一個(gè)域相同男图,就可以直接使用域和錯(cuò)誤代碼享言。在Mac OS環(huán)境下,用戶字典即使為nil荧琼,只要指定了域名和錯(cuò)誤代碼命锄,多數(shù)情況下警告面板中也會(huì)顯示日語的錯(cuò)誤消息偏化。
錯(cuò)誤反應(yīng)鏈
通過將NSError結(jié)合應(yīng)用框架來使用侦讨,就可以在GUI環(huán)境中靈活的進(jìn)行錯(cuò)誤處理。核心模塊是錯(cuò)誤反應(yīng)鏈(error-responder chain)骗污。
錯(cuò)誤反應(yīng)鏈的結(jié)構(gòu)
錯(cuò)誤反應(yīng)鏈與反應(yīng)鏈同樣沈条,是位于底層的組件指向上層組件的對象(反應(yīng)器)的連接蜡歹。
當(dāng)與某個(gè)組件相關(guān)的錯(cuò)誤發(fā)生時(shí)月而,會(huì)將錯(cuò)誤對象作為參數(shù)將消息presentError:發(fā)送給該組件。于是錯(cuò)誤對象就會(huì)從該組件開始被轉(zhuǎn)發(fā)給上層的組件仲翎。最終由窗體發(fā)送給NSApplication實(shí)例溯香,錯(cuò)誤提示和恢復(fù)被執(zhí)行。
錯(cuò)誤對象的更改和恢復(fù)
因發(fā)生錯(cuò)誤傳遞的錯(cuò)誤對象以及用戶字典是常數(shù)對象结笨,所以willPresentError:必須要返回一個(gè)新創(chuàng)建的錯(cuò)誤對象。而且原錯(cuò)誤對象使用新錯(cuò)誤對象用戶字典中NSUnderlyingErrorKey鍵并保存它湿镀。
包含多個(gè)字符串時(shí)炕吸,第一個(gè)字符串在面板的最右側(cè)(默認(rèn)),然后是右邊第二個(gè)勉痴,按照此順序排列赫模。也可能返回nil,此時(shí)警告面板中只顯示“OK”鍵蒸矛。
接著瀑罗,在取得按鍵的選擇結(jié)果后,指定對象來執(zhí)行恢復(fù)處理雏掠。該對象就稱為恢復(fù)嘗試對象(recovery attempter)。
恢復(fù)嘗試對象可以是任意類的實(shí)例乡话,但為了進(jìn)行恢復(fù)摧玫,需要實(shí)現(xiàn)NSErrorRecoveryAttempting非正式協(xié)議的其中任意一個(gè)方法。該非正式協(xié)議在Foundation/NSError.h中聲明绑青。