NSNull异常处理

NSNull异常处理:https://github.com/nicklockwood/NullSafe

介绍

在和服务端交互数据时,可能会出现 、null,客户端解析时会 Crash,为了减少程序Crash 的发生,可以使用 NullSafe 这个类别。NullSafe对不识别的类型返回 nil,而不是抛出异常,造成Crash。

使用时只需要把 NullSafe.m 文件放入工程即可。

分析

首先,对于不能处理的消息,会进行消息转发,如果我们没有处理,那么程序会异常退出。

简述NullSafe的整个处理过程:

  • 消息转发时,先从缓存signatureCache字典中通过selectorString为key获取消息的签名,签名存在则直接返回。
  • 签名不存在时,从集合classList中获取能够处理相应消息的类,并获取签名,保证线程安全。
  • 若缓存classList不存在,通过运行时objc_getClassList方法,获取所有已注册类,存在集合classList中。
  • 最后把消息签名,添加到缓存的signatureCache字典中,以方法名selectorString为key存储,方便再次使用。
  • methodSignatureForSelector方法没有找到能够处理消息的签名,返回nil,消息转发不往下执行,此时程序依旧Crash。
  • 如果最终找到能够处理消息的签名对象,程序执行forwardInvocation方法,将消息发给nil,程序不Crash。

缓存部分的代码逻辑:

static NSMutableSet<Class> *classList = nil;
static NSMutableDictionary<NSString *, id> *signatureCache = nil;
static void cacheSignatures()
{
    classList = [[NSMutableSet alloc] init];
    signatureCache = [[NSMutableDictionary alloc] init];

    //get class list
    int numClasses = objc_getClassList(NULL, 0);
    Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    //add to list for checking
    for (int i = 0; i < numClasses; i++)
    {
        //determine if class has a superclass
        Class someClass = classes[i];
        Class superclass = class_getSuperclass(someClass);
        while (superclass)
        {
            if (superclass == [NSObject class])
            {
                [classList addObject:someClass];
                [classList removeObject:[someClass superclass]];
                break;
            }
            superclass = class_getSuperclass(superclass);
        }
    }

    //free class list
    free(classes);
}

核心方法

int objc_getClassList(Class *buffer, int bufferCount)

第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount:数组中可存放元素的个数,返回值是注册的类的总数。


查找消息签名部分代码:

//find implementation
for (Class someClass in classList)
{
    if ([someClass instancesRespondToSelector:selector])
    {
        signature = [someClass instanceMethodSignatureForSelector:selector];
        break;
    }
}

通过测试代码测试,如:

[[NSNull null] performSelector:@selector(isEqualToString:) withObject:nil afterDelay:0];

简易版本

朋友给我分享的一简易版本,也是通过运行时消息转发:

#import "NSNull+XSafe.h"
#import <objc/runtime.h>

@implementation NSNull (XSafe)

- (id)forwardingTargetForSelector:(SEL)aSelector {
    __block id target = nil;
    [[self proxyObjects] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj respondsToSelector:aSelector]) {
            target = obj;
            *stop = YES;
        }
    }];
    NSLog(@"XSafe: [NSNull %@] unrecognized selector", NSStringFromSelector(aSelector));
    return target;
}

- (NSArray *)proxyObjects {
    NSArray *objs = objc_getAssociatedObject(self, _cmd);
    if (!objs) {
        objs = @[@0, @"", @{}, @[], [NSDate new]];
        objc_setAssociatedObject(self, _cmd, objs, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    return objs;
}

@end

从上文中提到的objc_getClassList方法,获取到的已注册的类,约1~2万,每次异常时消息转发都会遍历去找消息签名,从效率上肯定是“简易版本”更高,只不过NullSafe更全面。