iOS 中的對于異常采用處理方式

開發(fā)過過程中我們經(jīng)常會遇到異常問題

iOS異常.png

我們在程序開發(fā)過程中經(jīng)常會遇到異常诬像,對異常的處理一般采用打印或者直接拋出货邓。這樣也可以很方便我們調(diào)試過程有所參考立莉,而且方便我們查看異常產(chǎn)生的位置信息

NSError(錯誤信息)

采用NSError的情況

使用 NSError 的形式可以把程序中導(dǎo)致錯誤原因回報給調(diào)用者茉稠,而且使程序正常運(yùn)行不會造成奔潰的后果

NSError包含的內(nèi)容

@interface NSError : NSObject <NSCopying, NSSecureCoding> {
    @private
    void *_reserved;
    NSInteger _code;
    NSString *_domain;
    NSDictionary *_userInfo;
}
  • NSError _code(錯誤碼累榜, 類型 NSInteger) : 獨(dú)有的錯誤代碼削解,指明在某個范圍內(nèi)具體發(fā)生的錯誤富弦。可能是一系列的錯誤的集合氛驮,所以可以使用 Enum 來定義腕柜。
  • NSError *_domain (錯誤范圍,類型 NSString) :錯誤發(fā)生范圍矫废,通常使用一個特有的全局變量來定義盏缤。
  • NSError *_userInfo (用戶信息,類型 NSDictionary) :有關(guān)錯誤的額外信息蓖扑,其中包含一段“本地化的描述”唉铜,還可能包含導(dǎo)致此錯誤發(fā)生的另一個錯誤。userInfo 可以描述為一條錯誤的鏈條

NSError使用的兩種情況

在方法實(shí)現(xiàn)或者是 API 設(shè)計時律杠,我們對 NSError 的使用情形有兩種:在協(xié)議中傳遞和“輸出參數(shù)”返回給調(diào)用者

  1. 通過委托協(xié)議來傳遞錯誤
- (void)connection:(NSURLConnection *)connection withError:(NSError *)error;

通過代理協(xié)議的方式可以把錯誤報告信息傳遞

2.“輸出參數(shù)”返回給調(diào)用者

- (BOOL)doSomething:(NSError **)error;

錯誤的信息作為一個指向指針的指針(也就是說一個指針指向錯誤的對象)

@throw NSException (異常拋出)

采用 @throw 情況

在 APP 運(yùn)行期間遇到問題潭流,需要對問題進(jìn)行操作終止程序拋出異常,可以使用 @throw 來進(jìn)行

采用 @throw 可能產(chǎn)生問題

在異常拋出實(shí)例中柜去,如果拋出異常灰嫉。在實(shí)現(xiàn)拋出異常的代碼后面的執(zhí)行釋放資源就不會執(zhí)行,這樣末尾的對象就不會被釋放嗓奢。如果想要生成“異常安全”的代碼讼撒,可以設(shè)置編譯器的標(biāo)志 -fobjc-arc-exceptions 來進(jìn)行實(shí)現(xiàn)。不過將要一如加一些額外的代碼股耽,在不拋出異常時也會執(zhí)行代碼根盒。
在不使用 ARC 時也很難實(shí)現(xiàn)異常拋出情況下對內(nèi)存進(jìn)行釋放。

NSError * error = nil;
BOOL success = [self doSomething:&error];
if(error && success) {
     @throw[NSException …];
}
[success release];

按照上面??實(shí)現(xiàn)當(dāng)在異常拋出的過程時物蝙,success 還來不及釋放炎滞。所以要解決上面的問題可以在異常拋出之前對 success 進(jìn)行釋放,但是當(dāng)需要釋放的資源有很多的情況下茬末,這樣操作起來就比較復(fù)雜。

在開發(fā)過程中異常拋出可以使用在 抽象的基類 實(shí)例方法中進(jìn)行調(diào)用。如果基類中的方法不被 override 就設(shè)置拋出異常丽惭,這樣可以保證實(shí)例方法被重寫击奶。

@try @catch @finally (異常捕捉)

采用 @try @catch @finally 情況

個人感覺 @try @catch @finally 是 @throw NSException 的加強(qiáng)版。前者可以實(shí)現(xiàn)對異常的捕捉责掏,相關(guān)異常的輸出柜砾,和異常輸出后執(zhí)行的 @finally 相關(guān)的具體操作。后者是對具體整理 NSExpection 拋出换衬,并沒有前者比較完善的流程化操作步驟痰驱。

如果在基類中實(shí)現(xiàn) @throw 進(jìn)行設(shè)置 必須 override 基類中的實(shí)例方法,那么捕獲異常的方法中使用 @try @catch @finally瞳浦。例如:NSArray担映,NSDictionary 初始變量使用字面量來獲取值時,需要判斷返回數(shù)據(jù)是否為 nil 來防止 APP Crash叫潦。

產(chǎn)生問題和解決方式

當(dāng)使用 @try @catch @finally 時蝇完,有兩種情況 MRCARC

MRC(iOS 5 之前) 的環(huán)境中矗蕊,上面的代碼可以展示為:

SomeClass *someClass = nil;
@try {
       someClass = [[SomeClass alloc] init];
       [someClass doSomeThingsThatMayThrow];
}
@catch() {
     NSLog(… …);
}
@finally {
     [someClass release];
}

在 ARC 的環(huán)境中短蜕,上面的代碼展示為:

SomeClass *someClass = nil;
@try {
       someClass = [[SomeClass alloc] init];
       [someClass doSomeThingsThatMayThrow];
}
@catch( … ) {
     NSLog(… …);
}
@finally {
    
}

可以看出在 MRC 的情形下可以實(shí)現(xiàn)對于內(nèi)存的釋放,在 ARC 的情形下會系統(tǒng)會實(shí)現(xiàn)對內(nèi)存的釋放? 這樣向正確嗎傻咖?

答案是:ARC 不會自動處理朋魔,因?yàn)槿绻獙?shí)現(xiàn)自動處理可能要加入大量的代碼,才可以清楚對象實(shí)現(xiàn)拋出異常時將其清理卿操。但是如果加入代碼就會影響運(yùn)行時的性能警检,在正常運(yùn)行時也會如此。
如果在當(dāng)前實(shí)現(xiàn)中開啟 -fobjc-arc-exception 的模式可以實(shí)現(xiàn)在 @try @catch @finally 在異常情況下實(shí)現(xiàn)對未釋放的對象進(jìn)行內(nèi)存的釋放管理

@try@catch@finally 的 C++ 源碼

查看異常拋出的源碼:
建立項(xiàng)目在 main.m 文件中實(shí)現(xiàn)下面代碼:

int main(int argc, char * argv[]) {
    
    @try {
        
    } @catch (NSException *exception) {
        
    } @finally {
        
    }
}

打開終端在 main.m 終端的文件夾路徑執(zhí)行下面的語句

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

會生成文件 main.cpp 的文件硬纤,可以打開查看文 @try @catch @fianlly

NSExpection 屬性內(nèi)容
__attribute__((__objc_exception__))

#ifndef _REWRITER_typedef_NSException
#define _REWRITER_typedef_NSException
typedef struct objc_object NSException;
typedef struct {} _objc_exc_NSException;
#endif

struct NSException_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //實(shí)例變量
    NSString *name;                      //exception 的名字
    NSString *reason;                    //exception 產(chǎn)生的原因
    NSDictionary *userInfo;              //exception 展示使用詳細(xì)的信息
    id reserved;                         //
};
NSError 屬性內(nèi)容
#ifndef _REWRITER_typedef_NSError
#define _REWRITER_typedef_NSError
typedef struct objc_object NSError;
typedef struct {} _objc_exc_NSError;
#endif

struct NSError_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //實(shí)例變量
    void *_reserved;                     //實(shí)例調(diào)用的方法
    NSInteger _code;                     //error 錯誤碼
    NSString *_domain;                   //error 錯誤發(fā)生范圍
    NSDictionary *_userInfo;             //error 錯誤描述的具體信息
};

下面在 main.m 中使用 Clang 解析 @try @catch @finally 在 C++ 環(huán)境中解析

<ul>
<li>沒有參數(shù)
</li>

<li>有參數(shù)
</li>
</ul>

沒有參數(shù)

在沒有參數(shù)的情況下

int main() {
    
    @try {
        
    } @catch (NSException *exception) {
        
    } @finally {
        
    }
}

經(jīng)過 Clang 解析后源碼如下:

int main() {

{ id volatile _rethrow = 0;
    try {
        try {
                 //異常捕獲
            } catch (_objc_exc_NSException *_exception) {
                 NSException *exception = (NSException*)_exception; 
                 //捕獲異常解滓,并進(jìn)行拋出
            } 
        }
    catch (id e) {
         _rethrow = e;
        }
    {  
      struct _FIN { _FIN(id reth) : rethrow(reth) {}
        ~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
        id rethrow;
        } _fin_force_rethow(_rethrow);
           //異常拋出后執(zhí)行的 finally 的內(nèi)容
        }
    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
有參數(shù)

在有參數(shù)的情況下

int main() {
    
    NSDictionary *dic = @{@"name":@"liang",
                          @"last name":@"bai",
                          @"dream":@"to be a bussinessman",
                          @"work":@"IT"};
    
    NSString *salaryLiterals = nil;
    NSString *salaryAtIndex = nil;
    @try {
        
        salaryAtIndex = [dic objectForKey:@"salary"];
        salaryLiterals = dic[@"salary"];
        
    } @catch (NSException *exception) {
        NSLog(@"error name is %@, reason : %@, userInfo : %@", exception.name, exception.reason, exception.userInfo);
    } @finally {
        
    }
}

經(jīng)過 Clang 解析后源碼如下:

int main() {
    NSDictionary *dic = ((NSDictionary *(*)(Class, SEL, const ObjectType *, const id *, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSDictionary"), 
sel_registerName("dictionaryWithObjects:forKeys:count:"), 
(const id *)__NSContainer_literal(4U, (NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_1,
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_3, 
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_5, 
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_7).arr,
(const id *)__NSContainer_literal(4U, (NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_0, 
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_2,
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_4,
(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_6).arr, 4U);
    NSString *salary = __null;
    { id volatile _rethrow = 0; //使用 volatile 修飾的 _rethrow 記錄異常的局部變量 
        try {
            try {
                    salary = ((id  _Nullable (*)(id, SEL, KeyType))(void *)objc_msgSend)((id)dic, sel_registerName("objectForKeyedSubscript:"), 
              (id)(NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_8);
                } 
            catch (_objc_exc_NSException *_exception) {
             NSException *exception = (NSException*)_exception; 
             NSLog((NSString *)&__NSConstantStringImpl__var_folders_tv_4c43vcqx24vcxx6d51n4ntc00000gn_T_main_753a25_mi_9,
                    ((NSExceptionName (*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("name")), 
                    ((NSString * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("reason")), 
                    ((NSDictionary * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("userInfo")));
            } 
        }
        catch (id e) {
            _rethrow = e;
        }
         { 
             struct _FIN { 
                _FIN(id reth) : rethrow(reth) {}
                ~_FIN() { 
                  if (rethrow) objc_exception_throw(rethrow); 
                }
                id rethrow;
             } 
             _fin_force_rethow(_rethrow);
                salary = __null;
         }
    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

總結(jié):

(1)遇到奔潰問題或者是錯誤問題,優(yōu)先使用 NSError 來對奔潰和錯誤進(jìn)行封裝筝家,然后使用 NSLog 對其進(jìn)行打印

(2)@try @catch @finally 在使用的過程中很方便洼裤,但是 MRC 中如果變量較多可能會漏掉局部變量內(nèi)存釋放問題和 ARC 中如果拋出問題,不會自動對局部變量釋放(開啟 -fobjc-arc-expections 模式會進(jìn)行釋放溪王,但是引入代碼對性能有所影響)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腮鞍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子莹菱,更是在濱河造成了極大的恐慌移国,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件道伟,死亡現(xiàn)場離奇詭異迹缀,居然都是意外死亡使碾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門祝懂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來票摇,“玉大人,你說我怎么就攤上這事砚蓬∈该牛” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵灰蛙,是天一觀的道長祟剔。 經(jīng)常有香客問我,道長摩梧,這世上最難降的妖魔是什么物延? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮障本,結(jié)果婚禮上教届,老公的妹妹穿的比我還像新娘。我一直安慰自己驾霜,他們只是感情好案训,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粪糙,像睡著了一般强霎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蓉冈,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天城舞,我揣著相機(jī)與錄音,去河邊找鬼寞酿。 笑死家夺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伐弹。 我是一名探鬼主播拉馋,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼惨好!你這毒婦竟也來了煌茴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤日川,失蹤者是張志新(化名)和其女友劉穎蔓腐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龄句,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡回论,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年散罕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傀蓉。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡笨使,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僚害,到底是詐尸還是另有隱情,我是刑警寧澤繁调,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布萨蚕,位于F島的核電站,受9級特大地震影響蹄胰,放射性物質(zhì)發(fā)生泄漏岳遥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一裕寨、第九天 我趴在偏房一處隱蔽的房頂上張望浩蓉。 院中可真熱鬧,春花似錦宾袜、人聲如沸捻艳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽认轨。三九已至,卻和暖如春月培,著一層夾襖步出監(jiān)牢的瞬間嘁字,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工杉畜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纪蜒,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓此叠,卻偏偏與公主長得像纯续,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拌蜘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容