封装YYModel的实践2018

字典转模型需要吗?

  • 如果是Swift编程,要不要确实是一个问题;如果是Object-C,那么大概率需要。动态特性很有优势,这是一个很典型的应用场景。

  • 后台返回的字段确实太多,并且还有很多没有用的字段,有这么一个自动转模型的工具,确实方便。

  • 用模型比直接用字典在使用中要方便很多。

选哪个第三方库?

  • 自己写这样一个工具?难度有点大,尽量不要。还是选择成熟的第三方库比较靠谱。

  • YYModel作者自己写的比较文章能帮助我们决策,非常赞:
    iOS JSON 模型转换库评测

  • 类的静态方法是使用起来最方便的,不过这样接口的第三方库没有。

  • 类别,确实是Object-C中的一大特色,感觉有点黑科技,不过使用起来确实还是蛮方便的。利用已有的对象,直接使用方法,省去了特意创建对象的麻烦。

  • 直接在NSObject上添加方法,而这又是Object-C中所有对象的基类,应用范围确实非常广。

  • 简单好用,在实际使用中也没有遇到不适用的场景,所以就选这个了。
    YYModel

如何封装?

  • 想把类别接口转换为类的静态方法,感觉没有必要。还要多传一个类名的字符串,增加了麻烦。

  • 既然保留类别的方式,那么就改一下类别的名字,比如NSObject+KJTModel

封装哪些方法?

  • 网络返回的json对象一般有NSDictionary, NSString or NSData三种,工程中使用的是NSDictionary,那么就不要用id类型参数了,直接把类型确定为NSDictionary,所以封装接口+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
    当然,名字要换一个,比如下面这样的:
+ (nullable instancetype)kjtModelWithDictionary:(NSDictionary *)dictionary {
    return [self yy_modelWithDictionary:dictionary];
}

这是自定义类的静态方法,不是YYModel的静态方法,也不是KJTModel的静态方法。比如User *user = [User kjtModelWithDictionary: dictionary];

  • 函数- (nullable id)yy_modelToJSONObject;不封装。一般输入参数比较简单,比如一个id之类的。另外输入参数转json对象,一般网络库在创建NSRULRequest的时候都会做,这里没有必要重复

封装哪些方便方法?

下面这些方便方法是YYModel顺便提供的,不过在实际应用中会用到,因此这里也中转一下。

  • 自定义类型copy,这样就没有必要一行一行写了。
- (nullable id)kjtObjectCopy {
    return [self yy_modelCopy];
}
  • 序列化或者缓存要实现的KVC编解码函数:
- (void)kjtEncodeWithCoder:(NSCoder *)aCoder {
    [self yy_modelEncodeWithCoder:aCoder];
}

- (id)kjtInitWithCoder:(NSCoder *)aDecoder {
    return [self yy_modelInitWithCoder:aDecoder];
}
  • 自定义对象的比较:
- (BOOL)kjtIsEqualToObject:(id)object {
    return [self yy_modelIsEqual:object];
}
  • 自定义对象的成员名称,以后断点调试的时候,只要[self kjtObjectDescription]一下,就再也不仅仅是一个看不懂的指针地址了:
- (NSString *)kjtObjectDescription {
    return [self yy_modelDescription];
}

NSArray的方法

有些时候,需要返回一个列表,回来的结果可能是这样的:

    @{@"list":
              @[@{@"name": @"张三"},
              @{@"name": @"李四"},
              @{@"name": @"王五"}]               
    };

这时候如果定义两个Model,第一级只有一个list成员,感觉意义不大。所以,不如先把list的数组先拿出来,再转Model,更有意义一点。
所以,YYModel提供了一个NSArray的类别,来满足这种需求,感觉挺有用的。

+ (nullable NSArray *)kjtModelArrayWithClass:(Class)cls json:(id)json {
    return [NSArray yy_modelArrayWithClass:cls json:json];
}

这里是指NSArray的静态方法[NSArray kjtModelArrayWithClass:cls json: json];

NSObject的协议函数:

增加了几个可选的协议函数,让用户自定义,实现一些有用功能。

@protocol YYModel <NSObject>
@optional
...
@end
  • 如果后台定义的字段叫做id该怎么办呢?这是Object-C中的关键字。最好的方法当然是跟后台协商,把名字换成比如userId之类的。如果后台不配合,那么可以实现下面的协议函数:
@implementation User
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"id"  : @"UserId"};
}
@end

函数的原型是这样的,作者也给出了很好的注释:

/**
 Custom property mapper.
 
 @discussion If the key in JSON/Dictionary does not match to the model's property name,
 implements this method and returns the additional mapper.
 
 Example:
    
    json: 
        {
            "n":"Harry Pottery",
            "p": 256,
            "ext" : {
                "desc" : "A book written by J.K.Rowling."
            },
            "ID" : 100010
        }
 
    model:
        @interface YYBook : NSObject
        @property NSString *name;
        @property NSInteger page;
        @property NSString *desc;
        @property NSString *bookID;
        @end
        
        @implementation YYBook
        + (NSDictionary *)modelCustomPropertyMapper {
            return @{@"name"  : @"n",
                     @"page"  : @"p",
                     @"desc"  : @"ext.desc",
                     @"bookID": @[@"id", @"ID", @"book_id"]};
        }
        @end
 
 @return A custom mapper for properties.
 */
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
  • 另外一个是容器字段,比如有个字段是NSArray类型的books,那么books包含哪些字段呢?这个作者提供了另外一个协议函数来描述,注释也很丰富:
/**
 The generic class mapper for container properties.
 
 @discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
 implements this method and returns a property->class mapper, tells which kind of 
 object will be add to the array/set/dictionary.
 
  Example:
        @class YYShadow, YYBorder, YYAttachment;
 
        @interface YYAttributes
        @property NSString *name;
        @property NSArray *shadows;
        @property NSSet *borders;
        @property NSDictionary *attachments;
        @end
 
        @implementation YYAttributes
        + (NSDictionary *)modelContainerPropertyGenericClass {
            return @{@"shadows" : [YYShadow class],
                     @"borders" : YYBorder.class,
                     @"attachments" : @"YYAttachment" };
        }
        @end
 
 @return A class mapper.
 */
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
  • 另外还有几个,比如黑白名单之类的,感觉作用没有前面两个普遍,这里就不列举了。

  • 这些协议函数,只要用户在自己的自定义类型里面定义了,在YYModel内部进行转化的时候,会调用到。如果要封装,换函数名字是不行的,换一个协议的名字倒是可以了。比如,创建一个头文件KJTModelProtocol.h将上面那两个协议函数的名字复制过来,起到类似函数说明的作用。

//
//  KJTModelProtocol.h
//  HaiLeBao
//
//  Created by zxs on 2018/7/30.
//  Copyright © 2018年 KJT. All rights reserved.
//

#ifndef KJTModelProtocol_h
#define KJTModelProtocol_h

/**
 If the default model transform does not fit to your model class, implement one or
 more method in this protocol to change the default key-value transform process.
 There's no need to add '<YYModel>' to your class header.
 */
// 这里只是换了一下协议的名字,也不影响原来的名称YYModel
@protocol KJTModelProtocol <NSObject>
@optional

/**
 Custom property mapper.
 
 @discussion If the key in JSON/Dictionary does not match to the model's property name,
 implements this method and returns the additional mapper.
 
 Example:
 
 json:
 {
 "n":"Harry Pottery",
 "p": 256,
 "ext" : {
 "desc" : "A book written by J.K.Rowling."
 },
 "ID" : 100010
 }
 
 model:
 @interface YYBook : NSObject
 @property NSString *name;
 @property NSInteger page;
 @property NSString *desc;
 @property NSString *bookID;
 @end
 
 @implementation YYBook
 + (NSDictionary *)modelCustomPropertyMapper {
 return @{@"name"  : @"n",
 @"page"  : @"p",
 @"desc"  : @"ext.desc",
 @"bookID": @[@"id", @"ID", @"book_id"]};
 }
 @end
 
 @return A custom mapper for properties.
 */
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;

/**
 The generic class mapper for container properties.
 
 @discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
 implements this method and returns a property->class mapper, tells which kind of
 object will be add to the array/set/dictionary.
 
 Example:
 @class YYShadow, YYBorder, YYAttachment;
 
 @interface YYAttributes
 @property NSString *name;
 @property NSArray *shadows;
 @property NSSet *borders;
 @property NSDictionary *attachments;
 @end
 
 @implementation YYAttributes
 + (NSDictionary *)modelContainerPropertyGenericClass {
 return @{@"shadows" : [YYShadow class],
 @"borders" : YYBorder.class,
 @"attachments" : @"YYAttachment" };
 }
 @end
 
 @return A class mapper.
 */
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;

@end

#endif /* KJTModelProtocol_h */

当然,这个文件完全是可有可无的,不提供完全没问题。

工程目录:

最后的输出文件可以叫做KJTModel.h,作为整体KJTKit.h的一部分。目录的样子也很简单:

image.png

虽然对外的接口是NSObject的类别,不过这块功能比较特殊,所以当做一种组件库单列出来,并没有并入KJTCatagory.h

参考文章

YYModel

iOS JSON 模型转换库评测

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

YYModel 学习

手把手带你撸一个 YYModel 的精简版

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 13,902评论 1 32
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型...
    龍飝阅读 6,634评论 0 12
  • 人来车往 我曾与你迷失在 十字路口 一开始我们迎头相遇 感动了彼此 也交换过信物 以为世界只在我们掌心 这一段斑马...
    黑色指纹阅读 1,144评论 0 3
  • 那还是2011年的春天,阿图正上高二,坐在前排的她总是因为上课说话被老师责骂,终于有一天班主任受不了她把她调到了...
    深爱如长风却是人悲凉阅读 2,934评论 3 2
  • 读书可以点亮生活,读书可以提升智慧,读书可以增加魅力,读书可以看见不一样的世界.读书是遇见最好自己的最捷径方法. ...
    蜜薛儿阅读 1,526评论 2 1

友情链接更多精彩内容