iOS 后台返回json解析出现的null的解决办法

 

在后台返回值为Null为空时,我们代码没有判断时,程序就会崩溃。当时一直很疑惑是为啥,后来发现是数据问题,由于服务器的数据库中有些字段为空,然后以Json形式返回给客户端时就会出现这样的数据。当我们通过一些第三方数据解析库解析数据后,null既不是字符串也不是nil所以我们有些判断是没用的。

null掉用一系列不存在的方法时就会crash;

null解析成对象之后,如果直接向这个对象发送消息(length,count 等等)就会直接崩溃。提示错误为

 

-[NSNull length]: unrecognized selector sent to instance 0x388a4a70

##解决办法 1.对取值对象类型判断。缺点是太过繁琐,每次都要写。

 

#define kDictIsEmpty(dic) ([dic isKindOfClass:[NSNull class]])//宏
 
if (![@"你要取的值" isKindOfClass:[NSNull class]]){
    //数据
}

2.字符串匹配。在获取到服务器返回的Json时(返回结果是string对象)通过stringByReplacingOccurrencesOfString方法,替换"null"为"空字符",然后解析。不过通过这个方法也有可能导致数据无法解析

 

 

json = [jsonStr  stringByReplacingOccurrencesOfString:@":null" withString:@""];

3.解析时把null 类型替换成nil。

 

//有返回值的宏
#define isToNull(value) \
({\
id tmp;\
if (![value isKindOfClass:[NSNull class]])\
tmp = value;\
else
tmp = nil;\
(tmp);\
})\
 
//调用方法
label.text = isToNull(dic[@"data"]);

4.如果使用是AFNetwork做网络请求的话,可以用以下代码,会自动去除空值

 

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFJSONResponseSerializer *response = [AFJSONResponseSerializer serializer];
response.removesKeysWithNullValues = YES;//去除空值
manager.responseSerializer = response;//申明返回的结果是json类

 

5.最有效而且简单的方法。使用国外一位大牛写的Category,叫做NullSafe ,在运行时操作,把空值置为nil,而nil是安全的,可以向nil对象发送任何message而不会奔溃。 具体使用方法项目有说明

https://github.com/nicklockwood/NullSafe

 

=======================nil、Nil、NSULL、NULL之间的区别

一、nil------

对象掉用方法和设置属性,实际上就是时运行时给对象发送消息,让对象去执行,nil不会接受任何消息,所以不会执行任何方法和属性,所以不会报错;

 

我们给对象赋值时一般会使用object = nil,表示我想把这个对象释放掉;

 

或者对象由于某种原因,经过多次release,于是对象引用计数器为0了,系统将这块内存释放掉,这个时候这个对象为nil,我称它为“空对象”。(注意:我这里强调的是“空对象”,下面我会拿它和“值为空的对象”作对比!!!)

 

所以对于这种空对象,所有关于retain的操作都会引起程序崩溃,例如字典添加键值或数组添加新原素等,

 

NSMutableArray *muarr=[[NSMutableArray alloc]init];
NSMutableDictionary *mudict=[[NSMutableDictionaryalloc]init];
 
id object;
object=nil;//赋值为nil可以,但是不可以对已经释放掉的对象强引用;
[muarr addObject:object];//(崩溃)
[mudict setObject:object forKey:@"one"];//(崩溃)

 

 

二、NSNull

 

NSNull和nil的区别在于,nil是一个空对象,已经完全从内存中消失了,而如果我们想表达“我们需要有这样一个容器,但这个容器里什么也没有”的观念时,我们就用到NSNull,我称它为“值为空的对象”。如果你查阅开发文档你会发现NSNull这个类是继承NSObject,并且只有一个“+ (NSNull *) null;”类方法。这就说明NSNull对象拥有一个有效的内存地址,所以在程序中对它的任何引用都是不会导致程序崩溃的。(这里其实不是完全的,例如从服务器取回来的数据,如果其中有一个NSNUll对象的字典或者数组,那么就会出错)

 

 

NSMutableArray *muarr=[[NSMutableArray alloc]init];
NSMutableDictionary *mudict=[[NSMutableDictionaryalloc]init];
 
id object;
object=nil;
if(object==nil){
    object=[NSNull null];
}
[muarr addObject:object];//不会崩溃
[mudict setObject:object forKey:@"one"];//不会崩溃

 

 

三、Nil

 

 

nil和Nil在使用上是没有严格限定的,也就是说凡是使用nil的地方都可以用Nil来代替,反之亦然。只不过从编程人员的规约中我们约定俗成地将nil表示一个空对象,Nil表示一个空类。

id object;
object=nil;
if(object==nil){
    NSLog(@"nil");
    
}
Class clas=Nil;
if(clas==Nil){
    NSLog(@"Nil");
}

 

 

四、NULL

我们知道Object-C来源于C、支持于C,当然也有别于C。而NULL就是典型C语言的语法,它表示一个空指针,参考代码如下:

int *ponit = NULL;
 
 
==============野指针

1.何为野指针?

 

野指针指指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

 

解释一下:

 

malloc 和 free 是在系统的栈上分配空间。

malloc是申请,意思就是告诉系统,我要用一块RAM,给我用了别人就不要用了。

free是释放,意思是告诉系统,给我的这块RAM我用完了,不再用了,系统可以把它干别的了。free()释放的是指针指向的内存。并不是说,free()解除了原来指向这块内存的指针与这块内存地址之间的联系,联系依然存在,这就可能导致误用出错,所以需要在free之后置NULL,这样就解除关联了。举个例子就是,A跟妻子离婚了,但A和她曾经是夫妻,曾经关系很亲密,这种关系不会随着离婚而改变,这是客观的,离婚了即free,A不能再去骚扰前妻,否则就是违法,A要是再想干那啥,A会坐牢的!!!

free之后,系统还没有拿这块RAM干别的事之前,这块RAM的内容可能是不会变的,依然可以读出原来的内容,因为你的指针a还是指向这块RAM。但要注意,这块RAM已经不属于你了,读一下内容无所谓,如果往里面写就很危险了。

2.成因

 

通俗说法:

 

如果程序定义了一个指针,就必须要立即让它指向一个我们设定的空间或者把它设为NULL,如果没有这么做,那么这个指针里的内容是不可预知的,即不知道它 指向内存中的哪个空间(即野指针),它有可能指向的是一个空白的内存区域,可能指向的是已经受保护的区域,甚至可能指向系统的关键内存,如果是那样就糟 了,也许我们后面不小心对指针进行操作就有可能让系统出现紊乱,死机了。所以我们必须设定一个空间让指针指向它,或者把指针设为NULL,这是怎么样的一 个原理呢,如果是建立一个与指针相同类型的空间,实际上是在内存中的空白区域中开辟了这么一个受保护的内存空间,然后用指针来指向它,那么指针里的地址就 是这个受保护空间的地址了,而不是不可预知的啦,然后我们就可以通过指针对这个空间进行相应的操作了;如果我们把指针设为NULL,我们在头文件定义中的 #define NULL 0 可以知道,其实NULL就是表示0,那么我们让指针=NULL,实际上就是让指针=0,如此,指针里的地址(机器数)就被初始化为0了,而内存中地址为0 的内存空间……不用多说也能想象吧,这个地址是特定的,那么也就不是不可预知的在内存中乱指一气的野指针了。

 

  还应该注意的是,free和delete只是把指针所指的 内存给释放掉,但并没有把指针本身干掉。指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不 把p设置为NULL,会让人误以为p是个合法的指针。用free或delete释放了内存之后,就应立即将指针设置为NULL,防止产生“野指针”。内存 被释放了,并不表示指针会消亡或者成了NULL指针。(而且,指针消亡了,也并不表示它所指的内存会被自动释放。)

 

 

指针变量未初始化

 

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

 

指针释放后之后未置空

 

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

 

指针操作超越变量作用域

 

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

 

 

一、什么是空指针和野指针

1.空指针

1> 没有存储任何内存地址的指针就称为空指针(NULL指针)

2> 空指针就是被赋值为0的指针,在没有被具体初始化之前,其值为0。

下面两个都是空指针:

NULL;
 
nil;

 

2.野指针

"野指针"不是NULL指针,是指向"垃圾"内存(不可用内存)的指针。野指针是非常危险的。

二、野指针和空指针例子

接下来用一个简单的例子对比一下野指针和空指针的区别

1.首先,打开Xcode的内存管理调试开关,它能帮助检测垃圾内存

ios json解析数组 ios解析json崩溃_ios json解析数组

  

ios json解析数组 ios解析json崩溃_ios json解析数组_02

ios json解析数组 ios解析json崩溃_空指针_03

 

 

2.自定义Student类,在main函数中添加下列代码

Student *stu = [[Student alloc] init];
2
3 [stu setAge:10];
4
5
6[stu setAge:10];

 

 

运行程序,你会发现第6行报错了,是个野指针错误!

ios json解析数组 ios解析json崩溃_野指针_04

 

3.接下来分析一下报错原因

1> 执行完第1行代码后,内存中有个指针变量stu,指向了Student对象

 

Student *stu = [[Student alloc] init];

 

ios json解析数组 ios解析json崩溃_野指针_05

假设Student对象的地址为0xff43,指针变量stu的地址为0xee45,stu中存储的是Student对象的地址0xff43。即指针变量stu指向了这个Student对象。

 

2> 接下来是第3行代码

 

[stu setAge:10];

 

 

这行代码的意思是:给stu所指向的Student对象发送一条setAge:消息,即调用这个Student对象的setAge:方法。目前来说,这个Student对象仍存在于内存中,所以这句代码没有任何问题。

 

3> 接下来是第5行代码

 

[stu release];

这行代码的意思是:给stu指向的Student对象发送一条release消息。在这里,Student对象接收到release消息后,会马上被销毁,所占用的内存会被回收。

(release的具体用法会放到OC内存管理中详细讨论)

ios json解析数组 ios解析json崩溃_ios json解析数组_06

Student对象被销毁了,地址为0xff43的内存就变成了"垃圾内存",然而,指针变量stu仍然指向这一块内存,这时候,stu就称为了野指针!

 

4> 最后执行了第7行代码

 

 

[stu setAge:10];

 

 

这句代码的意思仍然是: 给stu所指向的Student对象发送一条setAge:消息。但是在执行完第5行代码后,Student对象已经被销毁了,它所占用的内存已经是垃圾内存,如果你还去访问这一块内存,那就会报野指针错误。这块内存已经不可用了,也不属于你了,你还去访问它,肯定是不合法的。所以,这行代码报错了!

 

4.如果改动一下代码,就不会报错

1
2
3 [stu setAge:10];
4
5
6
7 stu = nil;
8
9 [stu setAge:10];

 

 

注意第7行代码,stu变成了空指针,stu就不再指向任何内存了

ios json解析数组 ios解析json崩溃_野指针_07

因为stu是个空指针,没有指向任何对象,因此第9行的setAge:消息是发不出去的,不会造成任何影响。当然,肯定也不会报错。

 

5.总结

1> 利用野指针发消息是很危险的,会报错。也就是说,如果一个对象已经被回收了,就不要再去操作它,不要再尝试给它发消息。

2> 利用空指针发消息是没有任何问题的,也就是说下面的代码是没有错误的:

 

 

 

 

============僵尸对象

  • 野指针.
  • C语言: 当我们声明1个指针变量,没有为这个指针变量赋初始值.这个指针变量的值是1个垃圾指 指向1块随机的内存空间。
  • OC语言: 指针指向的对象已经被回收掉了.这个指针就叫做野指针.

  • 僵尸对象
  • . 内存回收的本质.
  • 申请1块空间,实际上是向系统申请1块别人不再使用的空间.
  • 释放1块空间,指的是占用的空间不再使用,这个时候系统可以分配给别人去使用.
  • 在这个个空间分配给别人之前 数据还是存在的.
  • OC对象释放以后,表示OC对象占用的空间可以分配给别人.
  • 但是再分配给别人之前 这个空间仍然存在 对象的数据仍然存在.
  • 僵尸对象: 1个已经被释放的对象 就叫做僵尸对象.
  • . 使用野指针访问僵尸对象.有的时候会出问题,有的时候不会出问题.
  • 当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候,这个时候其实是可以访问的.
  • 因为对象的数据还在.
  • 当野指针指向的对象所占用的空间分配给了别人的时候 这个时候访问就会出问题.
  • 所以,你不要通过1个野指针去访问1个僵尸对象.
  • 虽然可以通过野指针去访问已经被释放的对象,但是我们不允许这么做.
  • . 僵尸对象检测.
  • 默认情况下. Xcode不会去检测指针指向的对象是否为1个僵尸对象. 能访问就访问 不能访问就报错.
  • 可以开启Xcode的僵尸对象检测. 
  • 那么就会在通过指针访问对象的时候,检测这个对象是否为1个僵尸对象 如果是僵尸对象 就会报错.
  • . 为什么不默认开启僵尸对象检测呢?
  • 因为一旦开启,每次通过指针访问对象的时候.都会去检查指针指向的对象是否为僵尸对象.
  • 那么这样的话 就影响效率了.
  • . 如何避免僵尸对象报错.
  • 当1个指针变为野指针以后. 就把这个指针的值设置为nil
  • 僵尸对象无法复活.
  • 当1个对象的引用计数器变为0以后 这个对象就被释放了.
  • 就无法取操作这个僵尸对象了. 所有对这个对象的操作都是无效的.
  • 因为一旦对象被回收 对象就是1个僵尸对象 而访问1个僵尸对象 是没有意义.