YYModel 学习

如何集成?

  • 支持CocoaPods,在 Podfile 中添加 pod 'YYModel'

  • 支持Carthage,在 Cartfile 中添加 github "ibireme/YYModel"

  • 在需要使用的地方只需要包含“YYModel.h”这一个头文件就可以了。这个头文件没有具体内容,只是加了一层包含关系,方便使用。

  • 一些可用的API,都在文件“NSObject+YYModel.h”中,注释还是比较详细的。是NSObject的类别,所以对于自定义的Model可以直接使用。这个和JSONModel需要一个固定基类相比在用法上要简洁一点。
  • 定义也在文件“NSObject+YYModel.h”中,却是对NSArray和NSDictionary的类别。提供了方便方法,对于集合中的元素进行JSON -》Model的转化,将JSON对象的集合转变为Model的集合。

基本用法

JSON -》Model

  • + (nullable instancetype)yy_modelWithJSON:(id)json;
  • 这是一个类方法,直接使用,返回一个类的实例instancetype,相当于初始化函数[[xxx alloc] init];
  • 输入参数json的类型是id,动态类型;期望是一个JSON对象,类型可以是NSDictionary, NSString or NSData. 从网络上传过来什么可以直接用,很方便。
  • 不需要调用系统函数将JSON转换为Dictionary,YYModel里面已经做了这一步了。
  • 返回的实例instancetype有可能为nil

Model -》JSON

  • - (nullable id)yy_modelToJSONObject;
  • 这是一个实例方法。实例已经存在,将实例转换为JSON对象,然后网络传输,符合使用场景。
  • 返回值是id,动态类型;结果是一个JSON对象,类型可以是NSDictionary or NSArray,符合JSON的定义(字典或者数组)。JSON 数据格式
  • 返回的JSON对象有可能为nil

实际的例子

YYModel

JSON对象

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

Model定义

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

@implementation User
@end

JSON -》Model

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

Model -》JSON

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

NSArray JSON -》Model

对于JSON对象数组的情况,[{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}],对数组中的成员进行转换,得到一个Model的数组[user1, user2]

/**
 Provide some data-model method for NSArray.
 */
@interface NSArray (YYModel)

/**
 Creates and returns an array from a json-array.
 This method is thread-safe.
 
 @param cls  The instance's class in array.
 @param json  A json array of `NSArray`, `NSString` or `NSData`.
              Example: [{"name":"Mary"},{"name":"Joe"}]
 
 @return A array, or nil if an error occurs.
 */
+ (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;

@end
  • 这个方法对于返回JSON对象数组的网络请求比较方便,这是数组的类别,可以用数组直接调用,返回值就是一个Model的数组,省去了数组遍历的步骤
  • 数组的Model-》JSON的过程没有提供方便方法

NSDictionary JSON -》Model

对于JSON对象字典的情况,{"user1":{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, "user2": {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}}, 对字典中的值进行转换,key保持不变,得到一个值为Model的新字典{"user1" : user1, "user2" : user2}

/**
 Provide some data-model method for NSDictionary.
 */
@interface NSDictionary (YYModel)

/**
 Creates and returns a dictionary from a json.
 This method is thread-safe.
 
 @param cls  The value instance's class in dictionary.
 @param json  A json dictionary of `NSDictionary`, `NSString` or `NSData`.
              Example: {"user1" : {"name" : "Mary"}, "user2" : {name : "Joe"}}
 
 @return A dictionary, or nil if an error occurs.
 */
+ (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
@end
  • 对于字典,一般会定义一个与之对应的Model进行互相转换。这种将Model定义与字典的值进行对应的使用场景有点特殊。这种场景容易跟普通使用方法混淆,不推荐用。
  • 字典的Model-》JSON的过程没有提供方便方法

特殊情况处理

通过一个协议,方便让使用者来指定一些特殊情况的处理方法。这些方法都是可选的。

/**
 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 '' to your class header.
 */
@protocol YYModel 
@optional
@end

不需要加 是因为实现者(代理)是Model自己。这是特殊的用法。

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

协议方法:

+ (nullable NSDictionary *)modelCustomPropertyMapper;

JSON对象:

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

Model定义:

// 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

2. 容器类属性(NSArray/NSSet/NSDictionary)

协议方法:

+ (nullable NSDictionary *)modelContainerPropertyGenericClass;

Model定义:

@class Shadow, Border, Attachment;

@interface Attributes
@property NSString *name;
@property NSArray *shadows; //Array
@property NSSet *borders; //Set
@property NSMutableDictionary *attachments; //Dict
@end

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

其他的特性比如黑白名单,转换后的校验,自定义字典key对应的类等功能不是很常用。
Model中包含Model的方式是支持的,不需要特殊指定。对顶级类NSObeject加类别,优势就体现出来了。

对比测试

  • 在下载的包中有一个对比测试程序,ModelBenchmark.xcodeproj,重点聚焦在“性能”和“容错”两个方面。通过数据对比,还做了excel的图表,一目了然,视觉震撼比较大。

  • 同时还写了测评博客,更方便传播。相对于其他方案,“性能”提升非常明显,在博客里也解释了这点,让人比较信服。iOS JSON 模型转换库评测

  • GitHub user的例子是简单使用。weibo Model是比较复杂的例子,涉及key map,容器类处理等核心内容。

  • 序列化,要实现NSCoding的协议函数。还有深拷贝,判断是否相等,hash值等都是在Model定义时有可能涉及的内容。简单但繁琐,通过例子也给出了简洁的使用方法。

#define YYModelSynthCoderAndHash \
- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } \
- (id)initWithCoder:(NSCoder *)aDecoder { 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]; }

在实际使用中,用上面这个宏,不鼓励也不反对。
根据实际情况,实现上面的函数。使用YYModel后,只要一句话调用就好了,带来了比较大的便利。

  • 在测试“容错”那部分,logError是一个变量,是一个block,相当于一个匿名函数。在这个场景中,比有名函数调用要灵活很多。在log中,通过✅等字符很形象啊。

  • 对于崩溃的情况,使用了try catch结构。通过对比,可以看出FastEasyMappingJSONModel“容错”性能比较差,如果后台数据返回错误,很容易崩溃。YYModel的“容错”性能是最好的,并且“性能”有5~10倍的提升,这两个库是可以考虑替换掉。

  • 在“性能”测试中,用了CACurrentMediaTime()这个函数来统计时间,将for循环放在一个@autoreleasepool中。相当于[[NSDate data] timeIntervalSinceReferenceDate];``, 在QuartzCore`框架中。
    NSDate 、CFAbsoluteTimeGetCurrent、CACurrentMediaTime 的区别

begin = CACurrentMediaTime();
@autoreleasepool {
    for (int i = 0; i < count; i++) {
        NSDictionary *json = [user yy_modelToJSONObject];
        [holder addObject:json];
    }
}
end = CACurrentMediaTime();
  • 在一个大函数中,通过{}进行分块。有时候switch语句编译不过,将case下面的内容包起来就好了。基本上用到的场合不多。

数据结构

iOS开发-Runtime详解
Objective-C Runtime 运行时之三:方法与消息
Objective-C中的一些特殊的数据类型 id、nil、Nil、SEL

id

在文件objc/objc.h

/// A pointer to an instance of a class.
typedef struct objc_object *id;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

Class

在文件objc/objc.h

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

在文件objc/runtime.h

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本,但我们仍能从中获取一些有用信息。
OC之OBJC2_UNAVAILABLE

SEL

在文件objc/objc.h

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
  • 结构体objc_selector的定义找不到
  • 可以将SEL理解为方法名的hash值,可以加快方法的查找速度
  • C中函数名是函数实现的地址,而SEL只跟函数名有关,不涉及函数实现的地址。将函数名和函数实现分离,可以实现函数交换等功能。
  • SEL只跟方法名有关,跟参数无关,没有C++中的函数重载功能
  • Opaque Types

IMP

在文件objc/objc.h

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

Method

在文件objc/runtime.h

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

方法:SEL(函数名)、IMP(函数实现)、method_types(参数)的统一体。

Ivar

在文件objc/runtime.h

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

objc_property_t

在文件objc/runtime.h

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
  • Property没有找到
  • struct objc_property定义没有找到

Meta Class

  • 实例对象(instance),类(class),元类(meta class) 三者之间的关系通过isa指针联系起来
  • 类(class)存实例方法(- 开头的方法)
  • 元类(meta class)存类方法(+ 开头的方法)


    YYModel 学习_第1张图片
    class.jpg
  • 每个Class都有一个isa指针指向一个唯一的Meta Class
  • 每一个Meta Class的isa指针都指向最上层的Meta Class
  • 最上层的Meta Class的isa指针指向自己,形成一个回路
  • 每一个Meta Class的super class指针指向它原本Class的 Super Class的Meta Class。但是最上层的Meta Class的 Super Class指向NSObject Class本身
  • 最上层的NSObject Class的super class指向 nil
    深入浅出Cocoa之类与对象
    [Cocoa]深入浅出Cocoa 之动态创建类
    刨根问底Objective-C Runtime

YYClassInfo文件

根据原生的数据结构,进行的类抽象。

YYEncodingType

  • 自定义的类型,是一种NS_OPTIONS
  • 将类型,修饰符等信息整合在一个变量中,效率较高。总共用到了3个字节。由不同的mask来整合。
  • YYEncodingType YYEncodingGetType(const char *typeEncoding); 这个全局函数用来将字符转化为自定义的类型
  • Type Encoding
  • Declared Properties

YYClassIvarInfo

  • 对应Ivar数据结构
  • 构建方法也是从Ivar作为输入参数
    - (instancetype)initWithIvar:(Ivar)ivar;
  • 用到的系统API
    const char *ivar_getName(Ivar v);
    const char *ivar_getTypeEncoding(Ivar v);
    ptrdiff_t ivar_getOffset(Ivar v);

ptrdiff_t是C/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果。ptrdiff_t定义在stddef.h(cstddef)这个文件内。ptrdiff_t通常被定义为long int类型。
ptrdiff_t定义在C99标准中。

YYClassMethodInfo

  • 对应Method数据结构
  • - (instancetype)initWithMethod:(Method)method;通过Method创建
  • 用到的系统API
    SEL method_getName(Method m);
    IMP method_getImplementation(Method m);
    const char *method_getTypeEncoding(Method m);
    char *method_copyReturnType(Method m);
    unsigned int method_getNumberOfArguments(Method m);
    char *method_copyArgumentType(Method m, unsigned int index);

YYClassPropertyInfo

  • 对应struct objc_property_t数据结构
  • - (instancetype)initWithProperty:(objc_property_t)property;
  • 用到的系统API
    const char *property_getName(objc_property_t property) ;
    objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount);
    SEL NSSelectorFromString(NSString *aSelectorName);
  • 如果是类,通过函数Class objc_getClass(const char *name);获取属性的类型信息
  • getter和setter函数,如果没有,会生成默认的
if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }

YYClassInfo

  • 对应Class数据结构
  • + (instancetype)classInfoWithClass:(Class)cls;
  • 这里用了两个静态的字典来存储类信息。key是Class,value是YYClassInfo。考虑到类型嵌套,会有一大堆的类型信息需要保存。这里用了Core Fountdation的字典。这两个字典是单例。
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    lock = dispatch_semaphore_create(1);
});
  • 这里也用到了线程保护,使用的GCD
if (info) {
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
    dispatch_semaphore_signal(lock);
}
  • 用到的系统API
    Class class_getSuperclass(Class cls) ;
    BOOL class_isMetaClass(Class cls);
    Class objc_getMetaClass(const char *name);
    const char *class_getName(Class cls);
    NSString *NSStringFromClass(Class aClass);
    Method *class_copyMethodList(Class cls, unsigned int *outCount);
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount);

NSObject+YYModel文件

YYEncodingNSType

  • 这是NS_ENUM,简单枚举,没有位操作
  • 对应属性中的Foundation类型,比如NSString,NSNumber等
  • YYEncodingType是包括基本类型,比如int,double等
  • YYEncodingType中的YYEncodingTypeObject进一步细分,可以得到相应的YYEncodingNSType
  • 相关的静态全局函数是static force_inline YYEncodingNSType YYClassGetNSType(Class cls);

YYNSDateFromString

  • 这是一个静态全局函数
  • 功能是将NSString转化为对应的NSDate
  • 由于日期format的种类很多,这里引入了一个block的数组
  • 将NSString的字符个数作为数据的标号,这种思维比较奇特
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
....
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum

_YYModelPropertyMeta

  • YYClassPropertyInfo进一步信息整合
  • 这里对属性的类型进行判断:
    是一个Foundation类型,还是一个C数值类型,或者是一个容器类型等

_YYModelMeta

  • YYClassInfo进一步信息整合`
  • 遍历类所有的属性,直到根类
// Create all property metas.
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
YYClassInfo *curClassInfo = classInfo;
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
    for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
        if (!propertyInfo.name) continue;
        if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
        if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
        _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
                                                                propertyInfo:propertyInfo
                                                                     generic:genericMapper[propertyInfo.name]];
        if (!meta || !meta->_name) continue;
        if (!meta->_getter || !meta->_setter) continue;
        if (allPropertyMetas[meta->_name]) continue;
        allPropertyMetas[meta->_name] = meta;
    }
    curClassInfo = curClassInfo.superClassInfo;
}
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
  • 处理代理函数modelPropertyBlacklist---黑名单
  • 处理代理函数modelPropertyWhitelist---白名单
  • 处理代理函数modelContainerPropertyGenericClass---容器类型指定,支持class类型和字符串的类名
  • 将类所有的属性转换为_YYModelPropertyMeta数组
  • 处理代理函数modelCustomPropertyMapper---属性和JSON键名称的对应关系。这种对应关系支持.格式的链式关系和一对多的数组。保存在相应的_YYModelPropertyMeta成员中。
  • 做标记,判断用户是否自定义了以下协议函数:
    modelCustomWillTransformFromDictionary
    modelCustomTransformFromDictionary
    modelCustomTransformToDictionary
    modelCustomClassForDictionary

ModelSetContext

typedef struct {
    void *modelMeta;  ///< _YYModelMeta
    void *model;      ///< id (self)
    void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
  • 这是一个结构体
  • 将类信息(_YYModelMeta),类(model),JSON(NSDictionary)等放在一起。
  • model --- modelMeta --- dictionary;相互转化的两种结构通过一个中间过渡数据结构,整合在一起
  • 类型都是void *,是C的指针

函数调用流程

JSON -》Model

  1. 起点:+ (nullable instancetype)yy_modelWithJSON:(id)json;
  2. 将JSON对象转换为字典:+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json

这里用到了系统的JSON转字典API:+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

  1. 字典转模型:+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
  2. 设置模型属性:- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
  3. 利用函数CFDictionaryApplyFunction调用static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context);
    或者,利用函数CFArrayApplyFunction调用static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context);
  4. 上面两个函数,都调用全局函数:static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta); 所有的实际工作都在这里

Model -》JSON

  • API函数:- (nullable id)yy_modelToJSONObject;

id的类型是NSDictionary or NSArray,根据实际情况指定

  • 如果需要NSDictionary or NSArray的结果,那么调用另外两个API:
    - (nullable NSData *)yy_modelToJSONData;
    - (nullable NSString *)yy_modelToJSONString;

都是基于- (nullable id)yy_modelToJSONObject;的结果做格式转换,这里用到了系统API:+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;

  • 实际的工作在全局函数static id ModelToJSONObjectRecursive(NSObject *model);中完成

几个知识点

objc_msgSend

  • Object-C中的消息转发,最终都要转换为对函数objc_msgSend的调用
  • 调用objc_msgSend时,要强制转换为具体的函数指针
  • 高效编写代码的方法(九):了解objc_msgSend
  • 调用objc_msgSend警告处理
  • Dispatch Objective-C Messages Using the Method Function’s Prototype
//objc_msgSend(self,selector,@"test");
((void(*)(id, SEL, id))objc_msgSend)(self, selector, @"test");```
  • (int) doSomething:(int) x { ... }
  • (void) doSomethingElse {
    int (action)(id, SEL, int) = (int ()(id, SEL, int)) objc_msgSend;
    action(self, @selector(doSomething:), 0);
    }```
  • 这里调用的时候多了一个(void *),多了一步强制转换,本质上都是为了解决64位硬件上崩溃的的问题
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);```

double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);```

__bridge

  • 在ARC下,将Object-C指针转换为C指针
  • Object-C 指针 和 C 指针的相互转换 与ARC 并验证__bridge关键字的作用
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);

ModelSetContext是一个C的struct

ModelSetValueForProperty

作用:将JSON(根式是Dictionary)的value(类型是id)赋值给Model的属性
方式:通过setter函数来实现,objc_msgSendSEL参数基本上都是meta->_setter

case1:数字类型

  • 实现过程在全局函数中ModelSetNumberToProperty
  • 细分为布尔型,8位,16位,32位,64位整数,浮点数等等各种具体类型

case2:Foundation类型

  1. Model属性类型是NSString或者NSMutableString;对value(id)的具体类型做了容错处理:
    ** NSString:直接设置
    ** NSMutableString:
    转化为NSString之后,调用mutableCopy
    NSNumber:取属性stringValue
    ** NSData:**转化为NSString
    NSURL:取属性absoluteString
    NSAttributedString:取属性string

  2. Model属性类型是NSDecimalNumber;对value(id)的类型是NSDecimalNumberNSNumberNSString的情况做了容错处理

  3. Model属性类型是NSData;对value(id)的类型是NSString的情况做了容错处理

  4. Model属性类型是NSDate;对value(id)的类型是NSString的情况做了容错处理

  5. Model属性类型是NSURL;对value(id)的类型是NSString的情况做了容错处理

  6. Model属性类型是NSDictionaryNSSetNSArray等容器类型时,对容器中每个成员调用函数yy_modelSetWithDictionary,一层层深入下去。

case3:其他类型

都对value为nil的情况做了处理

  1. Model属性类型是id对象类型时,调用函数yy_modelSetWithDictionary,一层层深入下去。

  2. Model属性类型是Class类型时,如果value是NSString,则调用函数NSClassFromString进行转化,然后设置。如果是其他类型,则判断其“元类”class_isMetaClass是否存在。存在,则直接设置

  3. Model属性类型是SEL类型时,如果value是NSString,则调用函数NSSelectorFromString进行转化,然后设置。

  4. Model属性类型是Block类型时,将value强制转换为void (^)()进行设置。

  5. Model属性类型是structunionchar[10]等类型时,如果value是NSValue,则调用函数- (void)setValue:(nullable id)value forKey:(NSString *)key;进行设置。

  6. Model属性类型是void*char*等类型时,如果value是NSValue,则将value强制转换为NSValue,取属性pointerValue进行设置。进行设置。

个人意见

  1. 看上去只有两个文件,但是类有很多,用了很多的内部类。这种方式不是很认同。还是推荐一个类一个源文件的方式。当然,这里的场景是高内聚的一个整体,本来也是把一个类的各子成员(都是struct),还是比较合适的。
  2. 将所有的头文件都归总为一个YYMode.h,这种方式是非常好的,推荐使用。
  3. 对于NSArray这种集合提供方便方法;对于JSON对象,采用id类型,支持NSDictionary, NSString, NSData三种类型;在处理的时候,统一为NSDictionary。这种方式,统筹考虑了实现和使用的方便性,只是增加了几层函数调用,值得推荐。
  4. 协议的定义、使用者、实现者都是同一个(self,Model自己),这里是特殊的使用场景。
    一般情况下应该分3个文件(协议定义,使用者,实现者)或者2个文件(协议的定义放在使用者的文件中)。
  5. 采用协议的设计方式,让使用者对特殊使用场景做自定义,值得推荐
  6. 将_YYModelMeta(runtime中各种struct对应的类)作为转换的中间载体,思维很巧妙
  7. if后面只有一个语句,省略了{},这种习惯不是很好。
    if (num) [num class]; // hold the number

参考文章

YYModel
iOS JSON 模型转换库评测
JSON 数据格式
Objective-C Runtime Programming Guide

你可能感兴趣的:(YYModel 学习)