IOS訊息機制應用執行個體--異常處理

來源:互聯網
上載者:User

標籤:UI   建立   assm   end   sig   方法   父類   去除   apt   

IOS訊息機制應用執行個體--異常處理

最近發現了一個在項目中常用的異常處的工具NullSafe,分析了它的實現原理,不小心發現了一個小Bug,現將其分享出來,關於這篇文章的Demo已經上傳至GitHub,看完如有收穫,歡迎Star,如有疑問歡迎issue,大家一起學習。在IOS開發中我們可能會遇到下面的情景:伺服器給我們返回得某個欄位是null,比如someValue:null,這個時候我們利用第三方工具轉化之後會得到someValue = <null>,這個時候如果我們判斷這個someValue的類型,會看到其為:NSNull。那麼問題來了,如果這個someValue是要給控制項賦值,比如:someLabel.text = someVlaue,這個時候相當於someLabel.text = nil,顯然,是不會有問題的。但是有時候我們可能會給這個貌似是NSString的對象發送訊息(因為我們在Model裡定義了NSString * someValue),比如:[someValue length]。這個時候由於null這個對象沒有這個方法,也就是是說:null 這個對象不能處理這個訊息所有就會Crash,讓程式閃退。那麼我們怎樣處理來避免這種Crash呢?我們怎樣處理這個訊息呢?
首先我們來看一下NSObject.h中我們不常用到的幾個方法,以及它們的含義:

    + (BOOL)resolveClassMethod:(SEL)sel; // 判斷是否發現了這個method,如果發了,將其添加給該對象,並且返回YES,如果沒有返回NO。    + (BOOL)resolveInstanceMethod:(SEL)sel;// 同上類似    - (id)forwardingTargetForSelector:(SEL)aSelector;  // 這個方法來指定未被識別的訊息首先要指向得對象     + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;    // 遍曆一個類的執行個體方法得到這個訊息得NSMethodSignature,這個傳回值包含了對這個method相關的描述,如果這個method不能找到,那麼返回nil.     - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;//遍曆一個類的執行個體方法或者類方法來得到這個訊息的NSMethodSignature    - (void)forwardInvocation:(NSInvocation *)anInvocation; // NSObject的子類可以覆蓋這個方法來講訊息轉寄給其它的對象。當一個對象發送一個訊息,但是這個對象不能響應這個訊息,那麼RunTime會給這個對象一個來轉寄這個訊息的機會。 這個對象通過建立一個NSInvocation對象作為參數來調用這個forwardInvocation方法,然後改對象會調用這個方法來將訊息轉寄給其它對象。 這個NSInvocation對象其實是有上一個方法methodSignatureForSelector中返回的NSMethodSignature來得到的,所以在重寫這個方法之前我們必須重寫methodSignatureForSelector方法。    + (BOOL)instancesRespondToSelector:(SEL)aSelector;// 這個類的執行個體是否具有相應這個selector的能力,也就是說這個類有沒有這樣一個方法    - (IMP)methodForSelector:(SEL)aSelector;//遍曆一個類的執行個體方法或者類方法 得到這個方法的IMP(函數指標,指向這個方法的具體實現)    + (IMP)instanceMethodForSelector:(SEL)aSelector;//遍曆一個類的執行個體方法列表,得到這個方法IMP               - (void)doesNotRecognizeSelector:(SEL)aSelector; // 如果一個對象收到了一個訊息,但是它不能處理這個訊息,並且這個訊息沒有被轉寄,那麼系統將會調用這個方法。    同時這個方法會引發一個NSInvalidArgumentException,並且引發error.

那麼這幾個方法,在系統中是怎樣的調用順序呢?我們來看:


IOS中訊息處理得流程

從中可以看出在給一個對象發送訊息的時候,如果對象沒有對應的IML,那麼會調用對象所屬類的

+ (BOOL)resolveInstanceMethod:(SEL)sel

方法,然後看對於這個SEL對象是否可以執行,如果不可以執行則會調用

- (id)forwardingTargetForSelector:(SEL)aSelector

來找到一個對象處理這個方法(我們可以返回一個對象,這個對象可以處理這個方法),如果這個方法返回的是nil,那麼會調用這個對象的

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

這個方法,如果這方法調用之後,仍然沒有找到對應的NSMethodSignature,那麼會調用

- (void)doesNotRecognizeSelector:(SEL)aSelector

這個方法,並且拋出異常,如果這個時候返回了一個有效NSMethodSignature,那麼會調用

- (void)forwardInvocation:(NSInvocation *)anInvocation

到這裡訊息的處理結束。
那麼對於NSNull這個對象,我們如何讓它處理一個其自身不能處理的訊息呢?這時候,我們肯定會想到,將這個訊息傳遞給其它可以處理的對象。那麼問題來了:我們如何能找到這樣一個對象呢?這時候我們想到了RunTime,利用RunTimeobjc_getClassList方法,我們可以擷取整個項目中註冊得所有類(只要在項目中添加了這個類檔案,無論這個類是否被使用),這個時候我們可以過濾掉用不到的父類,以節約迴圈得次數,因為子類已經繼承了父類的方法,所以具有處理這個訊息得能力,之後首先利用上文提到的instancesRespondToSelector來判斷這個類是否可以響應這個訊息,如果可以響應,那麼可以利用上文提到的instanceMethodSignatureForSelector來得到這個NSMethodSignature,並且返回。通過上述分析,系統會調用這個forwardInvocation,這個是時候我們調用NSInvocation的invokeWithTarget這個方法來將這個訊息發送給nil,在OC中向一個nil發送任何訊息都不會引起程式Crash,至此一個由於伺服器返回資料異常而導致的Crash被解決了。這顯然增加了系統的容錯能力,在項目調試階段,可能由於資料不完善,所以可以利用這個方法來規避Crash,但是在資料基本完善之後,我們可以去掉這種方法以便我們在程式Crash的時候,及時提醒後台人員來完善資料。

附:NullSafe的實現詳解:
@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:這個方法會將所有的類緩衝,以及這些類的數量。我們需要提供一塊足夠大得緩衝來儲存它們,所以我們必須調用這個函數兩次。第一次來判斷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,則跳出迴圈,並且加入classList                    {                        // 將系統中用到的所有類都加到了ClassList中                        [classList addObject:someClass];                        break;                    }                    //父類不是NSObject,將其父類添加到excluded                    [excluded addObject:superclass];                     superclass = class_getSuperclass(superclass);                }            }            // 刪除所有含有子類的類            for (Class someClass in excluded)            {                [classList removeObject:someClass];            }            //釋放記憶體            free(classes);        }        // 首先檢測緩衝是否有這個實現        NSString *selectorString = NSStringFromSelector(selector);        signature = signatureCache[selectorString];        if (!signature)        {            //找到方法的實現            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];不知道作者是不是考慮了其他問題,也可能是由於其大意。



擊水湘江
連結:http://www.jianshu.com/p/b1de9404d7d9
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

IOS訊息機制應用執行個體--異常處理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.