cocoapods的安装和第三方库的配置之前的文章已有涉及,请参考:【iOS】AFNetworking的基本使用和【iOS】Masonry库的基本使用
常规解析JSON数据最基础的方法是使用NSJSONSerialization
,见这篇文章【iOS】JSON解析,这样处理数据的方法会有一些麻烦:
- 需要很小心地处理Model属性类型与dictionary中的数据对应类型,比如有一个
NSURL* url
的值,就需要将dict[@"url"]
中的NSString
类型转化成NSURL
类型,很多时候忘记转化就会导致对象类型不一致 - 若赋值的地方比较多,每修改一次属性,就需要把所有赋值的地方进行一次整体的更改,工作重复且枯燥
- 有时JSON数据如果有遗漏或者变化,不容易发现,比如上边JSON解析这篇文章中,若JSON数据不包含
age
,通过integerValue
方法就会把值赋为0
使用JSONModel就会自行赋值Model中的属性,相对于常规方法,大大简化了代码量和难度
目录
JSONModel简介
简单来说就是调用第三方开源库JSONModel可以简化NSData ------ JSON ------ Model
相互转化这一流程
当我们向服务器发送网络请求之后,通过JSONModel
把请求下来的json数据
解析成我们自定义的继承于JSONModel
的XXXModel
类,进而转化成我们熟悉的数据结构赋值给对象,供我们进行访问
JSONModel不仅使用非常方便,而且还会检查JSON数据的完整性,如果JSON数据不完整会返回nil
JSONModel还提供了基本的数据类型转换,比如服务器错将数字传成字符串的话,JSONModel
也会帮你转换成你期望的类型
核心数据模型JSONModel类
来简单分析一下JSONModel.h
声明文件的Property Protocol部分
这些协议里并没有约定任何方法,也不会用来实现,只是作为属性的一种标记
- 属性添加
Ignore
协议表示JSONModel不会对这个属性进行解析,使用这种方式来进行本地数据的管理 (属性值可以完全忽略)
a. 解析时完全忽略ta
b. 场景:该属性不需要从服务器数据中获取
objectivec
{
@"id":"777",
@"name":"Jacky",
@"age":19
}
@interface MyModel : JSONModel
@property (nonatomic, copy)NSString* id;
@property (nonatomic, copy)NSString* name;
@property (nonatomic, assign)NSInteger age;
//一般这个属性都是拼接上去、在本地操作的
@property (nonatomic, copy)NSString<Ignore>* gender;
@end
Optional
协议表示这个协议是可选的,即JSON数据中如果有这个属性就解析,如果没有就跳过 (属性值可以为空或null)
a. 某些属性值可以为空
b. 防止由于服务器返回数据为空导致JSONModel
异常(程序崩溃)- 可以看到以下两个协议被标记
DEPRECATED_ATTRIBUTE
,说明已被弃用,下面仅作以记录📝:
a.ConvertOnDemand
协议表示延迟加载 (懒加载) , 可以减少在网络读取时的性能消耗
b.Index
协议的作用是可以直接用索引访问该属性(在一个数组中被索引)
有了这些协议,在声明属性时,我们可以十分容易地设定ta们的解析规则,在JSONModel中,协议除了可以用来规定解析规则外,还可以用来指定 自定义数据类型的解析 ,只是我们需要自己定义一个协议,名称与自定义类名一致, 示例如下:
objectivec
@protocol Address : JSONModel
@end
@interface Address : JSONModel
@property (nonatomic, strong)NSString* info;
@end
@interface MyModel : JSONModel
@property (nonatomic, copy)NSString* id;
@property (nonatomic, copy)NSString* name;
@property (nonatomic, assign)NSInteger age;
@property (nonatomic, copy)NSString<Ignore>* gender;
//@property (nonatomic, strong)Address<Address>* address;
@property (nonatomic, strong)NSArray<Address>* address;
@end
如上代码所示,在解析数据时,会直接将address
数组中赋值为Address的对象,当然也可以像注释掉的那一行一样直接解析对象
JSONModel的基本使用
首先向服务器请求一个JSON数据 --- 【iOS】简单的网络请求
JSON数据来源:知乎日报API分析
对于Model集合、层级嵌套类型数据
a. 层级嵌套,Model中嵌套其他Model集合,将被嵌套的集合都写成一个类,且只需在同一个类文件中实现实现即可(例如:Stories
、Top_Sories
)
b. 包含其他Model集合的属性需要指定层级类型和自身类型(例如:NSArray<Stories> *
)
objectivec
@protocol StoriesModel
@end
@protocol Top_StoriesModel
@end
@interface StoriesModel : JSONModel
@property (nonatomic, copy) NSString* image_hue;
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* url;
@property (nonatomic, copy) NSString* hint;
@property (nonatomic, copy) NSString* id;
@end
@interface Top_StoriesModel : JSONModel
@property (nonatomic, copy) NSString* image_hue;
@property (nonatomic, copy) NSString* hint;
@property (nonatomic, copy) NSString* url;
@property (nonatomic, copy) NSString* title;
@property (nonatomic, copy) NSString* id;
@end
@interface TestModel : JSONModel
@property (nonatomic, copy) NSString *date;
@property (nonatomic, copy) NSArray<StoriesModel> *stories;
@property (nonatomic, copy) NSArray<Top_StoriesModel > *top_stories;
@end
设置所有属性可选(遵循Optional协议)
作用见上述Optional协议说明
objectivec
@implementation TestModel
+ (BOOL) propertyIsOptional:(NSString *)propertyName {
return YES;;
}
@end
//... ...其余两个类同理
JSON转换为Model
objectivec
//LatestStoriesModel* latestStoriesModel = [[LatestStoriesModel alloc] initWithData: data error: nil];
TestModel* model = [[TestModel alloc] initWithDictionary: responseObject error: nil];
将Model导出成字典、字符串
objectivec
//转化成字典
NSDictionary* dict = [model toDictionary];
//转化成字符串
NSString* string = [model toJSONString];
设置下划线自动转驼峰
a. 自定义把下划线字段解析为驼峰命名属性
b. 场景:服务器数据返回下划线命名字段可为Model中以驼峰命名的属性相应的赋值
c. mapperFromUpperCaseToLowerCase 大写转小写
objectivec
{
"order_id": 104,
"order_product" : @"Product#1",
"order_price" : 12.95
}
@interface OrderModel : BaseModel
@property (nonatomic, strong) NSString *orderId;
@property (nonatomic, assign) float orderPrice;
@property (nonatomic, strong) NSString *orderProduct;
@end
@implementation OrderModel
+ (JSONKeyMapper *)keyMapper
{
return [JSONKeyMapper mapperFromUnderscoreCaseToCamelCase];
}
@end
实例解析
objectivec
- (void)requestLatestStories {
NSString* jsonData = @"https://news-at.zhihu.com/api/4/news/latest";
jsonData = [jsonData stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL* url = [NSURL URLWithString: jsonData];
NSURLRequest* request = [NSURLRequest requestWithURL: url];
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionTask* task = [session dataTaskWithRequest: request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
TestModel* testModel = [[TestModel alloc] initWithData: data error: nil];
// NSLog(@"%@", testModel.stories[0].title);
// NSLog(@"%@", testModel.stories[0][@"title"]);
// StoriesModel* stories = testModel.stories[0];
// NSLog(@"%@", stories.title);
NSLog(@"%@", testModel.stories[0]);
}];
[task resume];
}
注意⚠️这里要访问title
以及跟它同一层级的数据时,用点语法或键值直接访问会报错,像这样:
因为在该页面下我们没有事先声明,此处声明一下即可:
如果将声明写成属性:
objectivec
@property (nonatomic, copy) StoriesModel *stories;
- 将访问到的
testModel.stories[0]
赋给刚才声明的属性,因为是在block
中进行的操作,所以不能直接使用_stories = testModel.stories[0];
- 在代码中使用
_stories
时,编译器将用self->_stories
替换代码,并且如果在块内使用它,则该块将捕获self
自身而不是stories
本身。 警告只是为了确保开发人员了解此行为。
objectivec
self->_stories = testModel.stories[0];
NSLog(@"%@",self->_stories.title);
这里涉及一点Block循环引用的知识,编者写过的分析Block文章也只是浅析,现阶段仅简单了解:
Block循环引用问题 首先什么是循环引用呢? 就是两个对象相互持有,在释放时,相互等待释放,造成死循环谁都释放不了,从而内存泄露。 即block作为self的属性时,又在block内部调用了self的属性和方法,block和self相互持有,那么两者的引用计数都至少是1,都不会被释放。
- 打印结果: