NSNull Crash處理 (NullSafe 的原理)

問題場景html

後端返回的數據中總會出現一些NSNull類型,當咱們一處理程序就會崩潰,所以想到把返回的數據中的NSNull類型所有轉換成@""空字符串git

(1)原始的json串:後端返回的json串裏面包含類型NSString,NSArray,NSDictionary,NSNull類型。github

 

{"status":1,"service_name":null,"service_id":null,"img_url":"http:\/\/api.jgfw.me\/assets\/uploads\/files\/","price":null,"num":3,"service_info":{"service_type":null,"service_time":null,"service_detail":null,"customer_name":null,"customer_phone":null,"customer_address":"","new_jishi":"","old_jishi":null,"lat":null,"lon":null},"order_info":{"order_no":"E15031267469289848688","pay_time":null,"order_time":null,"price":0,"order_state":null}}redis

(2)用SBJson庫:json串轉換成字典express

NSDictionary *jsonDic = [retString JSONValue];json

 

"<null>" 就是NSNull 類型,直接使用會Crash.segmentfault

 

摘要後端

NullSafe is a simple category on NSNull that returns nil for unrecognised messages instead of throwing an exception.api

 

  1 //
  2 //  NullSafe.m
  3 //
  4 //  Version 1.2.2
  5 //
  6 //  Created by Nick Lockwood on 19/12/2012.
  7 //  Copyright 2012 Charcoal Design
  8 //
  9 //  Distributed under the permissive zlib License
 10 //  Get the latest version from here:
 11 //
 12 //  https://github.com/nicklockwood/NullSafe
 13 //
 14 //  This software is provided 'as-is', without any express or implied
 15 //  warranty.  In no event will the authors be held liable for any damages
 16 //  arising from the use of this software.
 17 //
 18 //  Permission is granted to anyone to use this software for any purpose,
 19 //  including commercial applications, and to alter it and redistribute it
 20 //  freely, subject to the following restrictions:
 21 //
 22 //  1. The origin of this software must not be misrepresented; you must not
 23 //  claim that you wrote the original software. If you use this software
 24 //  in a product, an acknowledgment in the product documentation would be
 25 //  appreciated but is not required.
 26 //
 27 //  2. Altered source versions must be plainly marked as such, and must not be
 28 //  misrepresented as being the original software.
 29 //
 30 //  3. This notice may not be removed or altered from any source distribution.
 31 //
 32 
 33 
 34 //Fix  issue      desc = "<null>";icon = "<null>";
 35 
 36 
 37 #import <objc/runtime.h>
 38 #import <Foundation/Foundation.h>
 39 
 40 
 41 #ifndef NULLSAFE_ENABLED
 42 #define NULLSAFE_ENABLED 1
 43 #endif
 44 
 45 
 46 #pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"
 47 
 48 
 49 @implementation NSNull (NullSafe)
 50 
 51 #if NULLSAFE_ENABLED
 52 
 53 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
 54 {
 55     @synchronized([self class])
 56     {
 57         //look up method signature
 58         NSMethodSignature *signature = [super methodSignatureForSelector:selector];
 59         if (!signature)
 60         {
 61             //not supported by NSNull, search other classes
 62             static NSMutableSet *classList = nil;
 63             static NSMutableDictionary *signatureCache = nil;
 64             if (signatureCache == nil)
 65             {
 66                 classList = [[NSMutableSet alloc] init];
 67                 signatureCache = [[NSMutableDictionary alloc] init];
 68                 
 69                 //get class list
 70                 int numClasses = objc_getClassList(NULL, 0);
 71                 Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
 72                 numClasses = objc_getClassList(classes, numClasses);
 73                 
 74                 //add to list for checking
 75                 NSMutableSet *excluded = [NSMutableSet set];
 76                 for (int i = 0; i < numClasses; i++)
 77                 {
 78                     //determine if class has a superclass
 79                     Class someClass = classes[i];
 80                     Class superclass = class_getSuperclass(someClass);
 81                     while (superclass)
 82                     {
 83                         if (superclass == [NSObject class])
 84                         {
 85                             [classList addObject:someClass];
 86                             break;
 87                         }
 88                         [excluded addObject:NSStringFromClass(superclass)];
 89                         superclass = class_getSuperclass(superclass);
 90                     }
 91                 }
 92 
 93                 //remove all classes that have subclasses
 94                 for (Class someClass in excluded)
 95                 {
 96                     [classList removeObject:someClass];
 97                 }
 98 
 99                 //free class list
100                 free(classes);
101             }
102             
103             //check implementation cache first
104             NSString *selectorString = NSStringFromSelector(selector);
105             signature = signatureCache[selectorString];
106             if (!signature)
107             {
108                 //find implementation
109                 for (Class someClass in classList)
110                 {
111                     if ([someClass instancesRespondToSelector:selector])
112                     {
113                         signature = [someClass instanceMethodSignatureForSelector:selector];
114                         break;
115                     }
116                 }
117                 
118                 //cache for next time
119                 signatureCache[selectorString] = signature ?: [NSNull null];
120             }
121             else if ([signature isKindOfClass:[NSNull class]])
122             {
123                 signature = nil;
124             }
125         }
126         return signature;
127     }
128 }
129 
130 - (void)forwardInvocation:(NSInvocation *)invocation
131 {
132     invocation.target = nil;
133     [invocation invoke];
134 }
135 
136 #endif
137 
138 @end

當咱們給一個NSNull對象發送消息的話,可能會崩潰(null是有內存的),而發送給nil的話,是不會崩潰的。
做者就是使用了這麼一個原理,把發送給NSNull的而NSNull又沒法處理的消息通過以下幾步處理:緩存

  1. 建立一個方法緩存,這個緩存會緩存項目中類的全部類名。

  2. 遍歷緩存,尋找是否已經有能夠執行此方法的類。

  3. 若是有的話,返回這個NSMethodSignature

  4. 若是沒有的話,返回nil,接下來會走forwardInvocation:方法。

  5. [invocation invokeWithTarget:nil];將消息轉發給nil。

那麼,如何判斷NSNull沒法處理這個消息呢,在OC中,系統若是對某個實例發送消息以後,它(及其父類)沒法處理(好比,沒有這個方法等),系統就會發送methodSignatureForSelector消息,若是這個方法返回非空,那麼就去執行返回的方法,若是爲nil,則發送forwardInvocation消息。

這樣就完成整個轉發鏈了。

題外話:通常來講,咱們不該該在咱們的項目中使用NSNull類(大部分NSNull類的來源來自於接口的返回),而使用nil,在來源上,就應該堵上(要麼你解析到null進行處理,要麼和你的服務端說,不要給我返回null)。

 

reference:

1.https://segmentfault.com/q/1010000005064181

2.https://github.com/nicklockwood/NullSafe

3.http://blog.sina.com.cn/s/blog_5c91824f0102ve3c.html

相關文章
相關標籤/搜索