Source Code Learning - NullSafe

起源

代码中有时会处理跟 NSNull 相关的逻辑,有时候我们并不想要 NSNull,我们希望的是 nil,例如后台下发 json 出现如下数据

{
    "nullValue": null
}

这个时候用一般的库转换 JSON 为对象的时候,就会变为

{
    "nullValue": "<null>"
}

如果后台本来应该下发数组,而写代码的时候没有判断数据类型,例如直接获取 [array length],肯定会崩溃,出错原因是 [NSNull length] 没有对应的响应方法。

如何不手动判断类型,又能避免崩溃?NullSafe 就是为了解决这个问题而诞生的。

实现

NullSafe 只有短短的一百行代码。实现的方式是,创建 NSNull 的类别,在该类别中首先查找 NSNull 是否能响应某方法,如果不可以响应的话,就查找系统中是否有某个类可以处理调用的方法,如果可以的话,该方法转发给 nil,当作 nil 处理这个方法,例如 nil 调用 [nil length] 是不会抛出异常的。代码如下:

遍历所有系统中的类,循环遍历找每个类的 superClass,如果 superClass 是 NSObject,则加入到 classList

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);
}

methodSignatureForSelector 获取方法签名,在 cacheSignatures 中设置好的所有系统类中遍历,查看 classList 如果 classList 中有一个类能响应 selector 方法,则退出循环,返回该方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    //look up method signature
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature)
    {
        //check implementation cache first
        NSString *selectorString = NSStringFromSelector(selector);
        signature = signatureCache[selectorString];
        if (!signature)
        {
            @synchronized([NSNull class])
            {
                //check again, in case it was resolved while we were waitimg
                signature = signatureCache[selectorString];
                if (!signature)
                {
                    //not supported by NSNull, search other classes
                    if (signatureCache == nil)
                    {
                        if ([NSThread isMainThread])
                        {
                            cacheSignatures();
                        }
                        else
                        {
                            dispatch_sync(dispatch_get_main_queue(), ^{
                                cacheSignatures();
                            });
                        }
                    }
                    //find implementation
                    for (Class someClass in classList)
                    {
                        if ([someClass instancesRespondToSelector:selector])
                        {
                            signature = [someClass instanceMethodSignatureForSelector:selector];
                            break;
                        }
                    }
                    //cache for next time
                    signatureCache[selectorString] = signature ?: [NSNull null];
                }
                else if ([signature isKindOfClass:[NSNull class]])
                {
                    signature = nil;
                }
            }
        }
    }
    return signature;
}

只要有系统类能处理方法,那么就将 target 设置为 nil,消息转发给 nil,用 nil 去处理对应方法

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = nil;
    [invocation invoke];
}

进阶

NSNull+OVNatural

- (void)forwardInvocation:(NSInvocation *)invocation 
{ 
    if ([self respondsToSelector:[invocation selector]]) { 
        [invocation invokeWithTarget:self]; 
    } 
} 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector 
{ 
    NSMethodSignature *sig = [[NSNull class] instanceMethodSignatureForSelector:selector]; 
    if(sig == nil) { 
        sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; 
    } 
    return sig; 
} 

方法签名

// 从某个类中获取实例方法的方法签名

NSMethodSignature *sig = [[NSNull class] instanceMethodSignatureForSelector:selector]; 

// 从某个类中获取类方法签名

NSMethodSignature *signature = [super methodSignatureForSelector:selector];

// 创建方法签名

sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; 

手动创建一个方法签名,它封装了一个方法签名(不包含方法名,只有返回类型和参数类型)。

补充

AFNetWorking 的 AFJSONResponseSerializer 类上有个属性 removesKeysWithNullValues 设置该属性可以移除 null

参考文章

Type Encodings

请我喝汽水儿