起源
代码中有时会处理跟 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];
}
进阶
- (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