cocoapods的安装和第三方库的配置之前的文章已有涉及,请参考:【iOS】AFNetworking的基本使用【iOS】Masonry库的基本使用

常规解析JSON数据最基础的方法是使用NSJSONSerialization,见这篇文章【iOS】JSON解析,这样处理数据的方法会有一些麻烦:

  1. 需要很小心地处理Model属性类型与dictionary中的数据对应类型,比如有一个NSURL* url的值,就需要将dict[@"url"]中的NSString类型转化成NSURL类型,很多时候忘记转化就会导致对象类型不一致
  2. 若赋值的地方比较多,每修改一次属性,就需要把所有赋值的地方进行一次整体的更改,工作重复且枯燥
  3. 有时JSON数据如果有遗漏或者变化,不容易发现,比如上边JSON解析这篇文章中,若JSON数据不包含age,通过integerValue方法就会把值赋为0



JSONModel简介

简单来说就是调用第三方开源库JSONModel可以简化NSData —— JSON —— Model相互转化这一流程

当我们向服务器发送网络请求之后,通过JSONModel把请求下来的json数据解析成我们自定义的继承于JSONModelXXXModel类,进而转化成我们熟悉的数据结构赋值给对象,供我们进行访问

JSONModel不仅使用非常方便,而且还会检查JSON数据的完整性,如果JSON数据不完整会返回nil

JSONModel还提供了基本的数据类型转换,比如服务器错将数字传成字符串的话,JSONModel也会帮你转换成你期望的类型

核心数据模型JSONModel类

来简单分析一下JSONModel.h声明文件的Property Protocol部分

【iOS】JSONModel的基本使用-LMLPHP

这些协议里并没有约定任何方法,也不会用来实现,只是作为属性的一种标记

  • 属性添加Ignore协议表示JSONModel不会对这个属性进行解析,使用这种方式来进行本地数据的管理 (属性值可以完全忽略)
    a. 解析时完全忽略ta
    b. 场景:该属性不需要从服务器数据中获取
{
   @"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中, 示例如下:

@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】简单的网络请求

【iOS】JSONModel的基本使用-LMLPHP
JSON数据来源:知乎日报API分析

对于Model集合、层级嵌套类型数据

a. 层级嵌套,Model中嵌套其他Model集合,将被嵌套的集合都写成一个类,且只需在同一个类文件中实现实现即可(例如:StoriesTop_Sories
b. 包含其他Model集合的属性需要指定层级类型和自身类型(例如:NSArray<Stories> *)

@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协议说明

@implementation TestModel

+ (BOOL) propertyIsOptional:(NSString *)propertyName {
    return YES;;
}

@end
//... ...其余两个类同理

JSON转换为Model

//LatestStoriesModel* latestStoriesModel = [[LatestStoriesModel alloc] initWithData: data error: nil];
TestModel* model = [[TestModel alloc] initWithDictionary: responseObject error: nil];

将Model导出成字典、字符串

//转化成字典
NSDictionary* dict = [model toDictionary];
//转化成字符串
NSString* string = [model toJSONString];

设置下划线自动转驼峰

a. 自定义把下划线字段解析为驼峰命名属性
b. 场景:服务器数据返回下划线命名字段可为Model中以驼峰命名的属性相应的赋值
c. mapperFromUpperCaseToLowerCase 大写转小写

 {
   "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

实例解析

- (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以及跟它同一层级的数据时,用点语法或键值直接访问会报错,像这样:
【iOS】JSONModel的基本使用-LMLPHP
因为在该页面下我们没有事先声明,此处声明一下即可:
【iOS】JSONModel的基本使用-LMLPHP


如果将声明写成属性:

@property (nonatomic, copy) StoriesModel *stories;
  • 将访问到的testModel.stories[0]赋给刚才声明的属性,因为是在block中进行的操作,所以不能直接使用_stories = testModel.stories[0];

【iOS】JSONModel的基本使用-LMLPHP

  • 在代码中使用_stories时,编译器将用self->_stories替换代码,并且如果在块内使用它,则该块将捕获self自身而不是stories本身。 警告只是为了确保开发人员了解此行为。
	self->_stories = testModel.stories[0];
    NSLog(@"%@",self->_stories.title);

这里涉及一点Block循环引用的知识,编者写过的分析Block文章也只是浅析,现阶段仅简单了解:


  • 打印结果:
    【iOS】JSONModel的基本使用-LMLPHP
11-14 18:53