IOS消應(yīng)用實例--異常處理

IOS消息機制應(yīng)用實例--異常處理

親,我的簡書已不再維護和更新了育拨,所有文章都遷移到了我的個人博客:https://mikefighting.github.io/谨履,歡迎交流。

最近發(fā)現(xiàn)了一個在項目中常用的異常處理工具NullSafe至朗,分析了它的實現(xiàn)原理屉符,不小心發(fā)現(xiàn)了一個小Bug,現(xiàn)將其分享出來,關(guān)于這篇文章的Demo已經(jīng)上傳至GitHub矗钟,看完如有收獲唆香,歡迎Star,如有疑問歡迎issue吨艇,大家一起學(xué)習(xí)躬它。在IOS開發(fā)中我們可能會遇到下面的情景:服務(wù)器給我們返回得某個字段是null,比如someValue:null,這個時候我們利用第三方工具轉(zhuǎn)化之后會得到someValue = <null>,這個時候如果我們判斷這個someValue的類型东涡,會看到其為:NSNull冯吓。那么問題來了,如果這個someValue是要給控件賦值疮跑,比如:someLabel.text = someVlaue组贺,這個時候相當(dāng)于someLabel.text = nil,顯然,是不會有問題的祖娘。但是有時候我們可能會給這個貌似是NSString的對象發(fā)送消息(因為我們在Model里定義了NSString * someValue)失尖,比如:[someValue length]。這個時候由于null這個對象沒有這個方法,也就是是說:null 這個對象不能處理這個消息所有就會Crash渐苏,讓程序閃退淋纲。那么我們怎樣處理來避免這種Crash呢防嗡?我們怎樣處理這個消息呢巴柿?
首先我們來看一下NSObject.h中我們不常用到的幾個方法,以及它們的含義:

    + (BOOL)resolveClassMethod:(SEL)sel; // 判斷是否發(fā)現(xiàn)了這個method蜓萄,如果發(fā)了,將其添加給該對象鞠眉,并且返回YES,如果沒有返回NO薯鼠。
    + (BOOL)resolveInstanceMethod:(SEL)sel;// 同上類似
    
    - (id)forwardingTargetForSelector:(SEL)aSelector;  // 這個方法來指定未被識別的消息首先要指向得對象 
     
    + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
    // 遍歷一個類的實例方法得到這個消息得NSMethodSignature,這個返回值包含了對這個method相關(guān)的描述械蹋,如果這個method不能找到人断,那么返回nil.
     
     - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;//遍歷一個類的實例方法或者類方法來得到這個消息的NSMethodSignature
     
    - (void)forwardInvocation:(NSInvocation *)anInvocation; // NSObject的子類可以覆蓋這個方法來講消息轉(zhuǎn)發(fā)給其它的對象。當(dāng)一個對象發(fā)送一個消息朝蜘,但是這個對象不能響應(yīng)這個消息恶迈,那么RunTime會給這個對象一個來轉(zhuǎn)發(fā)這個消息的機會。
 這個對象通過創(chuàng)建一個NSInvocation對象作為參數(shù)來調(diào)用這個forwardInvocation方法谱醇,然后改對象會調(diào)用這個方法來將消息轉(zhuǎn)發(fā)給其它對象暇仲。
 這個NSInvocation對象其實是有上一個方法methodSignatureForSelector中返回的NSMethodSignature來得到的,所以在重寫這個方法之前我們必須重寫methodSignatureForSelector方法副渴。

    + (BOOL)instancesRespondToSelector:(SEL)aSelector;// 這個類的實例是否具有相應(yīng)這個selector的能力奈附,也就是說這個類有沒有這樣一個方法
    
    - (IMP)methodForSelector:(SEL)aSelector;//遍歷一個類的實例方法或者類方法 得到這個方法的IMP(函數(shù)指針,指向這個方法的具體實現(xiàn))
    
    + (IMP)instanceMethodForSelector:(SEL)aSelector;//遍歷一個類的實例方法列表煮剧,得到這個方法IMP           
    - (void)doesNotRecognizeSelector:(SEL)aSelector; // 如果一個對象收到了一個消息斥滤,但是它不能處理這個消息将鸵,并且這個消息沒有被轉(zhuǎn)發(fā),那么系統(tǒng)將會調(diào)用這個方法佑颇。
    同時這個方法會引發(fā)一個NSInvalidArgumentException顶掉,并且引發(fā)error.

那么這幾個方法,在系統(tǒng)中是怎樣的調(diào)用順序呢挑胸?我們來看下圖:

IOS中消息處理得流程

從中可以看出在給一個對象發(fā)送消息的時候痒筒,如果對象沒有對應(yīng)的IML,那么會調(diào)用對象所屬類的

+ (BOOL)resolveInstanceMethod:(SEL)sel

方法茬贵,然后看對于這個SEL對象是否可以執(zhí)行,如果不可以執(zhí)行則會調(diào)用

- (id)forwardingTargetForSelector:(SEL)aSelector

來找到一個對象處理這個方法(我們可以返回一個對象解藻,這個對象可以處理這個方法)老充,如果這個方法返回的是nil蚂维,那么會調(diào)用這個對象的

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

這個方法,如果這方法調(diào)用之后路狮,仍然沒有找到對應(yīng)的NSMethodSignature奄妨,那么會調(diào)用

- (void)doesNotRecognizeSelector:(SEL)aSelector

這個方法,并且拋出異常砸抛,如果這個時候返回了一個有效的NSMethodSignature,那么會調(diào)用

- (void)forwardInvocation:(NSInvocation *)anInvocation

到這里消息的處理結(jié)束树枫。
那么對于NSNull這個對象,我們?nèi)绾巫屗幚硪粋€其自身不能處理的消息呢砂轻?這時候,我們肯定會想到搔涝,將這個消息傳遞給其它可以處理的對象。那么問題來了:我們?nèi)绾文苷业竭@樣一個對象呢庄呈?這時候我們想到了RunTime,利用RunTimeobjc_getClassList方法蜕煌,我們可以獲取整個項目中注冊得所有類(只要在項目中添加了這個類文件,無論這個類是否被使用)诬留,這個時候我們可以過濾掉用不到的父類斜纪,以節(jié)約循環(huán)得次數(shù)贫母,因為子類已經(jīng)繼承了父類的方法,所以具有處理這個消息得能力盒刚,之后首先利用上文提到的instancesRespondToSelector來判斷這個類是否可以響應(yīng)這個消息腺劣,如果可以響應(yīng),那么可以利用上文提到的instanceMethodSignatureForSelector來得到這個NSMethodSignature,并且返回伪冰。通過上述分析誓酒,系統(tǒng)會調(diào)用這個forwardInvocation,這個是時候我們調(diào)用NSInvocation的invokeWithTarget這個方法來將這個消息發(fā)送給nil,在OC中向一個nil發(fā)送任何消息都不會引起程序Crash,至此一個由于服務(wù)器返回數(shù)據(jù)異常而導(dǎo)致的Crash被解決了。這顯然增加了系統(tǒng)的容錯能力贮聂,在項目調(diào)試階段靠柑,可能由于數(shù)據(jù)不完善,所以可以利用這個方法來規(guī)避Crash,但是在數(shù)據(jù)基本完善之后吓懈,我們可以去掉這種方法以便我們在程序Crash的時候歼冰,及時提醒后臺人員來完善數(shù)據(jù)。

附:NullSafe的實現(xiàn)詳解:
@implementation NSNull (NullSafe)

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    @synchronized([self class])
    {
        // 尋找 method signature
        NSMethodSignature *signature = [super methodSignatureForSelector:selector];
        if (!signature)
        {
            //改消息不能被NSNull處理耻警,所以我們要尋找其它的可以處理的類              
            static NSMutableSet *classList = nil;
            static NSMutableDictionary *signatureCache = nil;// 緩存這個找到的 method signature隔嫡,以便下次尋找
            if (signatureCache == nil)
            {
                classList = [[NSMutableSet alloc] init];
                signatureCache = [[NSMutableDictionary alloc] init];
            
            // 獲取項目中的所有類,并且去除有子類的類甘穿。
            // objc_getClassList:這個方法會將所有的類緩存腮恩,以及這些類的數(shù)量。我們需要提供一塊足夠大得緩存來存儲它們温兼,所以我們必須調(diào)用這個函數(shù)兩次秸滴。第一次來判斷buffer的大小,第二次來填充這個buffer募判。
            int numClasses = objc_getClassList(NULL, 0);             
            Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
            numClasses = objc_getClassList(classes, numClasses);
            

         
            NSMutableSet *excluded = [NSMutableSet set];
            for (int i = 0; i < numClasses; i++)
            {
                
                Class someClass = classes[i];
                
                // 篩選出其中含有子類的類荡含,加入:excluded中
                Class superclass = class_getSuperclass(someClass);
                while (superclass)
                {
                    if (superclass == [NSObject class]) // 如果父類是NSObject,則跳出循環(huán),并且加入classList
                    {
                        // 將系統(tǒng)中用到的所有類都加到了ClassList中
                        [classList addObject:someClass];
                        break;
                    }
                    
                    //父類不是NSObject,將其父類添加到excluded
                    [excluded addObject:superclass];
                     superclass = class_getSuperclass(superclass);
                }
            }

            // 刪除所有含有子類的類
            for (Class someClass in excluded)
            {
                [classList removeObject:someClass];
            }

            //釋放內(nèi)存
            free(classes);
        }
        
        // 首先檢測緩存是否有這個實現(xiàn)
        NSString *selectorString = NSStringFromSelector(selector);
        signature = signatureCache[selectorString];
        if (!signature)
        {
            //找到方法的實現(xiàn)
            for (Class someClass in classList)
            {
                if ([someClass instancesRespondToSelector:selector])
                {
                    signature = [someClass instanceMethodSignatureForSelector:selector];
                    break;
                }
            }
            
            //緩存以備下次使用
            signatureCache[selectorString] = signature ?: [NSNull null];
        }
        else if ([signature isKindOfClass:[NSNull class]])
        {
            signature = nil;
        }
    }
    return signature;
    }
    }
   
   - (void)forwardInvocation:(NSInvocation  *)invocation
   {
    // 讓nil來處理這個invocation
    [invocation invokeWithTarget:nil];
    
    }
    
  @end

在原文中作者是這樣寫的: [excluded addObject:NSStringFromClass(superclass)];
這樣ClassList中存放的是Class届垫,而excluded中存放的確是String,這樣就不能過濾掉不必要的類释液。所以,我將其改為了: [excluded addObject:superclass];不知道作者是不是考慮了其他問題装处,也可能是由于其大意误债。

延伸閱讀:

  1. http://www.reibang.com/p/8774e192d8db
  2. http://www.cocoabuilder.com/archive/cocoa/48930-objc-getclasslist-pointers-and-nsarray.html
  3. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妄迁,隨后出現(xiàn)的幾起案子找前,更是在濱河造成了極大的恐慌,老刑警劉巖判族,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異槽惫,居然都是意外死亡,警方通過查閱死者的電腦和手機仿耽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門项贺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭判,“玉大人林螃,你說我怎么就攤上這事⊥瓴校” “怎么了横漏?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵缎浇,是天一觀的道長华畏。 經(jīng)常有香客問我亡笑,道長,這世上最難降的妖魔是什么仑乌? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任晰甚,我火速辦了婚禮厕九,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扁远。我一直安慰自己,他們只是感情好并闲,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布谷羞。 她就那樣靜靜地躺著,像睡著了一般犀填。 火紅的嫁衣襯著肌膚如雪宏浩。 梳的紋絲不亂的頭發(fā)上靠瞎,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天乏盐,我揣著相機與錄音,去河邊找鬼神凑。 笑死何吝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓣喊。 我是一名探鬼主播黔酥,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棵帽,長吁一口氣:“原來是場噩夢啊……” “哼渣玲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仗谆,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藻雪,沒想到半個月后狸吞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年枢纠,在試婚紗的時候發(fā)現(xiàn)自己被綠了晋渺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脓斩。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖八千,靈堂內(nèi)的尸體忽然破棺而出恋捆,到底是詐尸還是另有隱情,我是刑警寧澤沸停,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布星立,位于F島的核電站葬凳,受9級特大地震影響室奏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昌简,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纯赎。 院中可真熱鬧犬金,春花似錦、人聲如沸峰伙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叽赊。三九已至,卻和暖如春必指,著一層夾襖步出監(jiān)牢的瞬間塔橡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癞谒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓双仍,卻偏偏與公主長得像朱沃,于是被迫代替她去往敵國和親逗物。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評論 0 9
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法蹂安,類相關(guān)的語法锐帜,內(nèi)部類的語法,繼承相關(guān)的語法允瞧,異常的語法蛮拔,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 大白健康系統(tǒng)--iOS APP運行時Crash自動修復(fù)系統(tǒng) 前言 大白(Baymax)建炫,迪士尼動畫《超能陸戰(zhàn)隊》中...
    鼠犬玉閱讀 17,443評論 22 158
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,140評論 30 470
  • 1.如何追蹤app崩潰率艺配,如何解決線上閃退 當(dāng) iOS設(shè)備上的App應(yīng)用閃退時转唉,操作系統(tǒng)會生成一個crash日志稳捆,...
    中婭沙漏閱讀 580評論 0 5