美文网首页学无止境DevSupportios开发学习
一篇文章全吃透—史上最全YYModel的使用详解

一篇文章全吃透—史上最全YYModel的使用详解

作者: CoderDancer | 来源:发表于2016-06-27 14:27 被阅读24966次

开篇说明:
虽然网上有很多讲解YYModel使用方法的文章,包括YYModel作者也在github上对其做了使用说明。
但在我实际使用过程中,依然发现文档的不完善,比如对于复杂的模型(如多层嵌套)讲解的仍不透彻,同时本文也会介绍一神器配合YYModel使用,让你感受分分钟搞定模型创建的酸爽。
当然为了减少读者的学习成本,本会对YYModel作者的文档进行丰富和扩展。
可在github上下载Demo,以便更直观了解各种使用场景详细代码。
文章只要包含:

    1. 详解YYModel的多种使用场景
    1. 拓展插件,让你一分钟搞定所有的模型的创建和调用。

一、YYModel的使用场景

1.简单的 Model 与 JSON 相互转换

// JSON:
{
    "uid":123456,
    "name":"Harry",
    "created":"1965-07-31T00:00:00+0000"
}

// Model:
@interface User : NSObject
@property UInt64 uid;
@property NSString *name;
@property NSDate *created;
@end

@implementation User

@end
// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model:
User *user = [User yy_modelWithJSON:json];

// 将 Model 转换为 JSON 对象:
NSDictionary *json = [user yy_modelToJSONObject];

JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。


格式自动转换.png

2.Model 属性名和 JSON 中的 Key 不相同

// JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}

// Model:
@interface Book : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation Book
//返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"name" : @"n",
             @"page" : @"p",
             @"desc" : @"ext.desc",
             @"bookID" : @[@"id",@"ID",@"book_id"]};
}
@end

你可以把一个或一组 json key (key path) 映射到一个或多个属性。如果一个属性没有映射关系,那默认会使用相同属性名作为映射。
在 json->model 的过程中:如果一个属性对应了多个 json key,那么转换过程会按顺序查找,并使用第一个不为空的值。
在 model->json 的过程中:如果一个属性对应了多个 json key (key path),那么转换过程仅会处理第一个 json key (key path);如果多个属性对应了同一个 json key,则转换过过程会使用其中任意一个不为空的值。

3.Model 包含其他 Model

// JSON
{
    "author":{
        "name":"J.K.Rowling",
        "birthday":"1965-07-31T00:00:00+0000"
    },
    "name":"Harry Potter",
    "pages":256
}

// Model: 什么都不用做,转换会自动完成
@interface Author : NSObject
@property NSString *name;
@property NSDate *birthday;
@end
@implementation Author
@end

@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author; //Book 包含 Author 属性
@end
@implementation Book
@end

4.容器类属性

@class Shadow, Border, Attachment;

@interface Attributes
@property NSString *name;
@property NSArray *shadows; //Array<Shadow>
@property NSSet *borders; //Set<Border>
@property NSMutableDictionary *attachments; //Dict<NSString,Attachment>
@end

@implementation Attributes
// 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"shadows" : [Shadow class],
             @"borders" : Border.class,
             @"attachments" : @"Attachment" };
}
@end

在实际使用过过程中,[Shadow class]Border.class@"Attachment"没有明显的区别。
这里仅仅是创建作者有说明,实际使用时,需要对其遍历,取出容器中得字典,然后继续字典转模型。(YYModel的核心是通过runtime获取结构体中得Ivars的值,将此值定义为key,然后给keyvalue值,所以我们需要自己遍历容器(NSArrayNSSetNSDictionary),获取每一个值,然后KVC)。

  • 具体的代码实现如下:
NSDictionary *json =[self getJsonWithJsonName:@"ContainerModel"];
ContainerModel *containModel = [ContainerModel yy_modelWithDictionary:json];
NSDictionary *dataDict = [containModel valueForKey:@"data"];
//定义数组,接受key为list的数组
self.listArray = [dataDict valueForKey:@"list"]; 
 //遍历数组
[self.listArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSDictionary *listDict = obj;
        //获取数组中得字典
        List *listModel = [List yy_modelWithDictionary:listDict];
        //获取count 和 id
        NSString *count = [listModel valueForKey:@"count"];
        NSString *id = [listModel valueForKey:@"id"];
       

5.黑名单与白名单

@interface User
@property NSString *name;
@property NSUInteger age;
@end

@implementation Attributes
// 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
+ (NSArray *)modelPropertyBlacklist {
    return @[@"test1", @"test2"];
}
// 如果实现了该方法,则处理过程中不会处理该列表外的属性。
+ (NSArray *)modelPropertyWhitelist {
    return @[@"name"];
}
@end

6.数据校验与自定义转换

实际这个分类的目的比较简单和明确。
就是对判断是否为时间戳,然后对时间戳进行处理,调用
_createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
获取时间。

// JSON:
{
    "name":"Harry",
    "timestamp" : 1445534567     //时间戳
}

// Model:
@interface User
@property NSString *name;
@property NSDate *createdAt;
@end

@implementation User
// 当 JSON 转为 Model 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
    NSNumber *timestamp = dic[@"timestamp"];
    if (![timestamp isKindOfClass:[NSNumber class]]) return NO;
    _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue];
    return YES;
}

// 当 Model 转为 JSON 完成后,该方法会被调用。
// 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。
// 你也可以在这里做一些自动转换不能完成的工作。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
    if (!_createdAt) return NO;
    dic[@"timestamp"] = @(n.timeIntervalSince1970);
    return YES;
}
@end
  • 需要注意的时,如果用插件,对时间戳类型或默认创建为NSUInteger类型,需要将其更改为NSDate类型。

7.Coding/Copying/hash/equal/description

以下方法都是YYModel的简单封装,实际使用过程和系统方法区别不大。对其感兴趣的可以点进方法内部查看。

@interface YYShadow :NSObject <NSCoding, NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) CGSize size;
@end

@implementation YYShadow
// 直接添加以下代码即可自动完成
- (void)encodeWithCoder:(NSCoder *)aCoder { 
 [self yy_modelEncodeWithCoder:aCoder]; 
}
- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super init];
  return [self yy_modelInitWithCoder:aDecoder]; 
}
- (id)copyWithZone:(NSZone *)zone { 
 return [self yy_modelCopy]; 
}
- (NSUInteger)hash { 
 return [self yy_modelHash]; 
}
- (BOOL)isEqual:(id)object { 
 return [self yy_modelIsEqual:object]; 
}
- (NSString *)description { 
 return [self yy_modelDescription]; 
}
@end

二、ESJsonFormat与YYModel的结合使用

彩蛋
给大家介绍一款插件,配合ESJsonFormat

配图:


ESJsonFormat插件使用.gif

使用方法:
快捷键:shift + control + J
插件安装方法比较简单,在此不赘述,不知道可自行google。

好处

    1. 可以直接将json数据复制,ESJsonFormat会根据数据类型自动生成属性。(建议还是要自行检查,比如时间戳,系统会默认帮你生成为NSUInteger,而我们想要的为NSDate类型)
    1. 对于多模型嵌套,不必创建多个文件,ESJsonFormat会自动在一个文件下创建多重类型,极其便捷。

至此YYModel的使用已讲解完毕,关于YYModel的底层核心是运用runtime获取类结构体中Ivars,进行KVC操作,然后根据不同情况进行分别处理
此处只是传递给大家一个概念,不展开讲解,网上有很多源码分析文章,可自学google学习。
文末,做个综述。
建议大家有时间一定要多看底层,分析源码。不要只会用,知其然不知其所以然。
如有错误欢迎指出。

写在最后

我得写作原则:
在技术学习道路上,阅读量和代码量绝不能线性提升你的技术水平。
同样写文章也是如此,作者所写的文章完全是基于自己对技术的理解,在写作时也力求形象不抽象。绝不copy充数,所以也欢迎大家关注和参与讨论。
技术学习绝不能孤胆英雄独闯天涯,而应在一群人的交流碰撞,享受智慧火花的狂欢。
希望我的文章能成为你的盛宴,也渴望你的建议能成为我的大餐。
如有错误请留言指正,对文章感兴趣可以关注作者不定期更新。

相关文章

网友评论

  • num_one:iOS自动生成Model兼容YYModel https://github.com/zhangs3721/ZZJsonToModel 了解一下
  • 136d130ea5e8:楼主理解有误,YYModel底层用的不是KVC,而是通过objc_msgSend动态调用对象的set方法给属性赋值的(((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);)。楼主可以仔细看下NSObject+YYModel.m文件中的这个方法:
    static void ModelSetValueForProperty(__unsafe_unretained id model,
    __unsafe_unretained id value,
    __unsafe_unretained _YYModelPropertyMeta *meta)
  • KA_STEM:完全自己不看吗,千篇一律,哪只眼看到是kvc ??误导人
  • 纯阳子_:谢谢老板分享干粮,吃透了
  • 7e603510ad92:我想问一下,如果是模型A里面包含了属性 B、C、D。 然后D又是一个模型,D包含熟悉 E、F、G。 我用YYModel正常解析没有问题。 但是当D返回NULL的时候,解析会崩溃,请问楼主有什么办法解决吗?
  • 口袋海贼王_:swift 用啥解析啊?
    CoderDancer:@口袋海贼王_ 可以根据项目自己选择
    口袋海贼王_:@CoderDancer 有推荐的嘛,比较好用的。
    CoderDancer:@口袋海贼王_ 类很多的,objectmapper等等,可以去自行搜索下
  • 萝卜酱紫:xcode9不能使用的吧插件
    萝卜酱紫:@CoderDancer 晓得了谢谢
    CoderDancer:不可以了
  • ad32582be739:《一篇文章全吃透—史上最全YYModel的使用详解- 简书》写的挺不错的,已经收藏了。

    源码解析:http://tinyurl.com/ycqn86hj


    8afe04d66ee7:写的不错,谢谢博主;已 收 藏~
  • CoderChou:解析数组json@CoderDancer,如何实现
  • bqqq:同样是第四点,在模型中实现 `+ modelContainerPropertyGenericClass`,替代原来的 `+ objectClassInArray` 就不用遍历了呀,你试试看
  • 晨风说产品:xcode8好像还不行哦
    AZander:@CoderDancer 怎么办,有没有类似的工具
    CoderDancer:@淳晨风 Xcode8不支持插件了
  • 码修:第四点。。。[NSArray yy_modelArrayWithClass:[ConversationModel class] json:request[@"data"]];这不是有直接的方法吗 。 我这个ConversationModel模型里面有 字典类型(子模型) 也有数组类型的 直接就转换了。他方法内部实现了遍历的,不用我们自己写啊
    num_one:iOS自动生成Model兼容YYModel https://github.com/zhangs3721/ZZJsonToModel 了解一下
  • 挂着铃铛的兔:我在使用你写的第3条的时候,遇到了问题,我模型包含模型,但
    BigModel *model = [BigModel yy_modelWithDictionary:dataDic];
    NSLog(@"%@d",model.shopCount.goods);
    这样写会报错,shopCount是一个模型,里面有goods这个参数。
  • 李国安:赞一个
    郝程序猿:@李国安 废话 哈哈哈哈哈哈
    李国安:@郝程序猿 你是? Hsy?
    郝程序猿:刚看见你 在这冒泡呢啊 哈哈
  • hhgvg:第三点 基本没这种情况 数据下来的时候怎么可能知道正好包含了哪个model的key呢
    CoderDancer:哈哈,越交流越深入,欢迎踢馆
    alexlee002:@alexlee002 我不是来踢馆的哈。。。。 只是做个广告。。。 楼主不要打我。。。。(逃。。。
    alexlee002:@hhgvg 这篇文章写的还是不够深入, 正好我在项目里引入YYModel之前也深入review过源码, 并且也跟作者沟通过,等下周有空了我也整一篇我对YYModel的扩展的文章。
  • 经文纬武:有没有什么方法能使xcode8也能用这个插件
  • 拿铁代码:Swift项目可以使用YYModel么?
    alexlee002:@拿铁代码 如果你的model是继承自NSObject的就可以
    CoderDancer:YYModel是基于运行时的,swift底层不是基于运行时的,所以一般不用YYModel
  • 笨拉高:唉,xocde8用不了插件!
    扛支枪:自己再下载个xcode7就行了呗
    CoderDancer:@笨拉高 ios8吸收了很多优秀的插件,但是有些插件却不支持了,等等看插件是否更新吧

本文标题:一篇文章全吃透—史上最全YYModel的使用详解

本文链接:https://www.haomeiwen.com/subject/unmzdttx.html