NSNull Crash处理 (NullSafe 的原理)

问题场景

后端返回的数据中总会出现一些NSNull类型,当我们一处理程序就会崩溃,因此想到把返回的数据中的NSNull类型全部转换成@""空字符串

(1)原始的json串:后端返回的json串里面包含类型NSString,NSArray,NSDictionary,NSNull类型。

 

{"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}}

(2)用SBJson库:json串转换成字典

NSDictionary *jsonDic = [retString JSONValue];

 

"<null>" 就是NSNull 类型,直接使用会Crash.

 

摘要

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

 

  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

posted @ 2016-09-26 10:19  学计算机的那个  阅读(2182)  评论(0编辑  收藏  举报