后台服务器返回给客户端的值有时会是null,直接赋值并进行后续操作有时会导致崩溃。
解决方法:
1.写个工具类,判断处理每个字段是不是[NSNull null]对象;
2.用AFNetworking作为网络请求的话,可以设置以下变量为YES:
// AFURLResponseSerialization.h
/**
Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`.
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
3.外国人写的一个NSNull分类:
https://github.com/nicklockwood/NullSafe
用法是直接拖到工程里。
NullSafe思路:在运行时操作,把空值置换为nil,而向nil指针发送消息是不会崩溃的。
//测试:模拟用后台返回的null对象赋值(OC对象的多态性)
NSString *str = [NSNull null];
//当调用str.length时,其实是调用NSNull对象的length方法,找不到此方法就会走下面的methodSignatureForSelector方法,来获取length方法签名
NSLog(@"%@",str.length);
//以下用performSelector调用方法时编译不报错,执行时同上去查找aaaaaa方法,若不实现的话应该找不到方法签名,就会crash掉,这种属于代码编写问题,crash掉才能发现问题~
[str performSelector:@selector(aaaaaa)];
源码阅读:
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
//若未定义NULLSAFE_ENABLED宏,则定义为1;若定义过NULLSAFE_ENABLED为0,下列代码则不执行,学习此种宏控制的代码执行方式
#ifndef NULLSAFE_ENABLED
#define NULLSAFE_ENABLED 1
#endif
#pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"
@implementation NSNull (NullSafe)
#if NULLSAFE_ENABLED
//NSNull对象找不到方法就会走这个方法。
//消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//synchronized线程锁
@synchronized([self class])
{
//取selector方法的签名
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
//not supported by NSNull, search other classes
//注意这里全局静态变量的使用技巧,下次调用时,以下变量不会被置nil了(静态变量不能重复初始化)
static NSMutableSet *classList = nil;
static NSMutableDictionary *signatureCache = nil;
if (signatureCache == nil)
{
classList = [[NSMutableSet alloc] init];
signatureCache = [[NSMutableDictionary alloc] init];
//------------获取所有根父类的类集合----------------------------------
//获取到当前注册的所有类的总个数
int numClasses = objc_getClassList(NULL, 0);
//根据 numClasses 个数调整分配类集合 classes 的空间
Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
//向已分配好内存空间的类集合 classes 中存放元素,并返回个数
numClasses = objc_getClassList(classes, numClasses);
//excluded存储含有父类的类
NSMutableSet *excluded = [NSMutableSet set];
for (int i = 0; i < numClasses; i++)
{
Class someClass = classes[i];
Class superclass = class_getSuperclass(someClass);
//如果父类是NSObject,说明已无父类,把class加到classList
//如果父类不是NSObject,把superClass加到excluded集合(自动剔除重复),继续遍历其父类,直到父类是NSObject,退出循环
while (superclass)
{
if (superclass == [NSObject class])
{
[classList addObject:someClass];
break;
}
[excluded addObject:NSStringFromClass(superclass)];
superclass = class_getSuperclass(superclass);
}
}
//移除所有含有父类的类,只保留根父类,原因下面会解释= =
for (Class someClass in excluded)
{
[classList removeObject:someClass];
}
//-------------------------------------------------------------------
//释放classes集合
free(classes);
}
//查找缓存集合
NSString *selectorString = NSStringFromSelector(selector);
signature = signatureCache[selectorString];
//在根父类的类集合classList中,查询某类是否实现selector方法
if (!signature)
{
for (Class someClass in classList)
{
//此方法会查询someClass及其子类是否有实现selector方法
if ([someClass instancesRespondToSelector:selector])
{
signature = [someClass instanceMethodSignatureForSelector:selector];
break;
}
}
//若获得了selector方法签名,赋值signatureCache[selectorString] = signature(学习此种三目运算使用技巧);
//若未获得,把静态字典signatureCache[selectorString] = [NSNull null];这样处理是为了若再次查找此selector方法的时候,直接在上面“signature = signatureCache[selectorString];”中signature获得为NSNull,这样就会走下面的else判断
signatureCache[selectorString] = signature ?: [NSNull null];
}
else if ([signature isKindOfClass:[NSNull class]])
{
//若未实现,就把selector方法签名置为nil,不会走下面forwardInvocation,直接crash,因为所有类都找不到此方法
signature = nil;
}
}
//signature若不为nil,接下来会走forwardInvocation方法
return signature;
}
}
//运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。
- (void)forwardInvocation:(NSInvocation *)invocation
{
//关键点在这里!将消息转发给nil
invocation.target = nil;
[invocation invoke];
}
源码学习点:
1、宏定义控制代码执行;静态变量作缓存防止再次获取筛选浪费资源;三目运算技巧;NSMutableSet集合无重复特点;
2、runtime的使用;
3、消息转发:对象找不到方法会调methodSignatureForSelector
获取方法签名。获得方法签名后调forwardInvocation
,此方法可以修改消息转发对象为nil;获取不到签名,直接crash掉,说明调用了一个未知方法,即写的代码出了问题;
4、为什么要找所有根父类的类集合呢?是因为instancesRespondToSelector
方法会先查询其子类是否有实现selector方法,再从子类的父类查找,如此直到查找到自身类;