YYModel

在网络请求的传输和解析中,免不了用到字典转模型的内容。YYModel源码研究。
地址:https://github.com/ibireme/YYModel

分析源码

就是runtime的应用。文件目录:

YYClassInfo

YYClassIvarInfo 对 Class的Ivar进行了进行封装增加描述
YYClassMethodInfo 对 Class 的 Method进行封装增加描述
YYClassPropertyInfo 对 Class 的 Property进行了封装描述
YYClassInfo 是对于Class进行了封装,进行封装增加描述

在runtime.h中obj_class定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

在YYClassInfo中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
class的描述信息.对一个class进行封装
*/
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< 元类
@property (nonatomic, readonly) BOOL isMeta; ///< 是否为元类
@property (nonatomic, strong, readonly) NSString *name; ///< 类名
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< 父类的描述信息
///< ivars 用字典来装,与YYClassIvarInfo一一对应
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos;
///< methods用字典来装,与YYClassMethodInfo一一对应
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos;
///< properties用字典来装,与YYClassPropertyInfo一一对应
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos;

/**
当class中有内容被修改了,如增加了一个新的方法,需要调用这个方法进行刷新class的信息,
这个方法调用后会将needUpdate方法中的返回值设置为YES,
从而需要调用classInfoWithClass或者classInfoWithClassName来获取刷新class的信息
*/
- (void)setNeedUpdate;

/**
classInfo是否需要刷新,当返回值为YES的时候,需要停止使用改Instance
*/
- (BOOL)needUpdate;

/**
获得cls的详细信息,并刷新
*/
+ (nullable instancetype)classInfoWithClass:(Class)cls;

/**
获得cls的详细信息,并刷新
*/
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;

@end

在YYClassInfo声明的方法里,我们可以看到YYClassInfo一旦class进行了修改,就要做对应的更新,这样的操作是为了能够高效缓存class的信息,如ivars,method,property,我们首先从这个类的入口方法开始看起.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 通过class获取对应class的信息,并且对这些信息进行处理
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
// 类 缓存容器
static CFMutableDictionaryRef classCache;
// 元类 缓存容器
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
// 为了线程安全 同步信号量
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// 是元类还是Class,从结果的容器中进行查找
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
// 是否找到info并且需要进行刷新
if (info && info->_needUpdate) {
[info _update];
}
// 释放信号量
dispatch_semaphore_signal(lock);
if (!info) { // info不在缓存容器中
** // 通过initWithClass重新创建一个info**
**info = [[YYClassInfo alloc] initWithClass:cls];**
if (info) {
// 缓存到对应的容器中
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// 再一次判断是meta还是class
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
dispatch_semaphore_signal(lock);
}
}
return info;
}

对一个类建立实例变量,属性,方法的索引之后,会对这个类进行缓存,以便下一次使用的时候不用重新建立索引,YYModel使用了 CFDictionaryCreateMutable 来进行缓存类的 info ,key 是类名,value 是 YYClassInfo 对象。使用 dispatch_semaphore_t 保证线程安全,之所以选择个是基于性能的考虑。

这里有几点需要说明一下:

mutable不是线程安全的,所以这里需要创建锁
创建缓存容器,如果在缓存容器中直接找到了class,则直接获取到对应的ivar,method,property,这样在下次访问到的时候就不用再去找
如果没有获取到,就开始创建一个YYClassInfo获得class中的描述信息

创建一个YYClassInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (instancetype)initWithClass:(Class)cls {
if (!cls) return nil;
self = [super init];
_cls = cls;
_superCls = class_getSuperclass(cls);
_isMeta = class_isMetaClass(cls);
if (!_isMeta) {
_metaCls = objc_getMetaClass(class_getName(cls));
}
// 获得类名
_name = NSStringFromClass(cls);
** [self _update];**

_superClassInfo = [self.class classInfoWithClass:_superCls];
return self;
}

YYClassInfo维护了一个变量来判断是否需要刷新,通过调用setNeedUpdate来修改_needUpdate=YES,进而调用_update方法来进行刷新.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
- (void)_update {
// 刷新ivar,method,property信息
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;

Class cls = self.cls;

// 取得类中的methods
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
if (methods) {
// 创建dictionary来进行缓存method信息
NSMutableDictionary *methodInfos = [NSMutableDictionary new];
_methodInfos = methodInfos;
// 遍历methods
for (unsigned int i = 0; i < methodCount; i++) {
YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
// 用方法名做key info做value
if (info.name) methodInfos[info.name] = info;
}
free(methods);
}

// 获取类中的property信息
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
if (properties) {
// 创建dictionary来进行缓存property信息
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
// 遍历property
for (unsigned int i = 0; i < propertyCount; i++) {
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
// 用属性名做key info做value
if (info.name) propertyInfos[info.name] = info;
}
free(properties);
}

// 取得类中的变量信息
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
if (ivars) {
// 创建dictionary来进行缓存ivar信息
NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
_ivarInfos = ivarInfos;
// 遍历ivar
for (unsigned int i = 0; i < ivarCount; i++) {
YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
// 用变量名做key info做value
if (info.name) ivarInfos[info.name] = info;
}
free(ivars);
}

// 如果当中有不存在的内容,把缓存dict置为空
if (!_ivarInfos) _ivarInfos = @{};
if (!_methodInfos) _methodInfos = @{};
if (!_propertyInfos) _propertyInfos = @{};

// 刷新结束
_needUpdate = NO;
}

YYClassIvarInfo

在runtime.h中ivar的定义:

1
2
3
4
5
6
7
8
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
}

在YYclassInfo中:

1
2
3
4
5
6
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name
@property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset变量偏移地址
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding变量的编码类型
@property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type转化成YYType类型

Creates and returns an ivar info object.创建,返回一个成员变量对象。可以直接在外面暴露的地方获取到这些属性。
- (instancetype)initWithIvar:(Ivar)ivar;

总结:

对于Ivar,property和Method都进行了一层封装,最后都服务于YYClassInfo,这样做的一个好处就是把原来里层的内容暴露在外层,方便查找,也可以进行缓存,提高访问效率和命中率,这也是对后面进行json转换做的一些工作

NSObject+YYModel

提供数据-模型方法:
1.把json转换成对象,或者把对象转换成Json.
2.设置对象的属性用KVO字典
3.‘NSCoding’,”NSCopying”,”-hash”.”-isEqual:”的实现

对映射处理的封装

_YYModelPropertyMeta

_YYModelPropertyMeta 是一个内部类,用来处理一个 Class 的 Property 的元数据类。和 YYClassPropertyInfo 不同的是,_YYModelPropertyMeta 还负责映射属性的维护。可以说是,对 YYClassPropertyInfo 更高层的封装,服务于映射的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 创建YYModelProperty的元类
@interface _YYModelPropertyMeta : NSObject {
@package
// property 名
NSString *_name;
// property 编码
YYEncodingType _type;
// Foundation类型
YYEncodingNSType _nsType;
// 是否为基础数据类型
BOOL _isCNumber;
// Class
Class _cls;
// 是否为集合类型即Array/Set/Dicitinoary
Class _genericCls;
// 属性的get方法和set方法
SEL _getter;
SEL _setter;
// 属性是否提供KVC方法
BOOL _isKVCCompatible;
// 属性是否支持归档
BOOL _isStructAvailableForKeyedArchiver;
// 是否有自定义的映射字典
BOOL _hasCustomClassFromDictionary;

// json 与属性映射的key 如 @{@"name":@"user"}
NSString *_mappedToKey;
// json 与属性映射的key是一个路径 @{@"name":@"person.name"}
NSArray *_mappedToKeyPath;
// json 与属性映射的key是一个数组,即一个key对应多个json key @{@"name":@[@"name",@"user",@"account"]}
NSArray *_mappedToKeyArray;
// 描述的property
YYClassPropertyInfo *_info;
**// 在多个属性映射一个json key 的时候使用**
**_YYModelPropertyMeta *_next;**
}
@end

多个property对应一个json key的情况,很可能映射表是这样的:

1
2
3
4
5
{
@"name" : @"name",
@"fullName" : @"name",
@"username" : @"name",
}

在这里name、fullName、username都对应json中name这个字段,我们拿刚刚这个json key 映射来做分析.


这样做有什么用呢,我们可以看到这样用了一个链表把所有映射到同一个json key的属性串联起来,这样如果json key 字段里的值改了,我们可以看到最先得到映射的是name属性,有了这样一个表,其他几个属性只需要通过next指针就可以拿到对应jsonkey 修改了的值,这样是不是直接把需要修改的都串联起来了呢..
YYModelPropertyMeta实现文件里的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
@implementation _YYModelPropertyMeta
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
// 创建一个meta对象
_YYModelPropertyMeta *meta = [self new];
// 将属性名赋值
meta->_name = propertyInfo.name;
// 赋值编码类型
meta->_type = propertyInfo.type;
// 将描述属性赋值
meta->_info = propertyInfo;
// 记录属性为容器类型的时候 元素的映射类型
meta->_genericCls = generic;

if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) { // 先匹配是否为NS类型 即Foundation 类型
meta->_nsType = YYClassGetNSType(propertyInfo.cls);
} else { // 是否为C数据类型
meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
}

// 属性为结构体类型
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
/*
It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
*/
static NSSet *types = nil;
static dispatch_once_t onceToken;
// 单例 创建一份c结构体类型映射
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet new];
// 32 bit
[set addObject:@"{CGSize=ff}"];
[set addObject:@"{CGPoint=ff}"];
[set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
[set addObject:@"{CGAffineTransform=ffffff}"];
[set addObject:@"{UIEdgeInsets=ffff}"];
[set addObject:@"{UIOffset=ff}"];
// 64 bit
[set addObject:@"{CGSize=dd}"];
[set addObject:@"{CGPoint=dd}"];
[set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
[set addObject:@"{CGAffineTransform=dddddd}"];
[set addObject:@"{UIEdgeInsets=dddd}"];
[set addObject:@"{UIOffset=dd}"];
types = set;
});

// 只有以上的结构体才能被归档
if ([types containsObject:propertyInfo.typeEncoding]) {
meta->_isStructAvailableForKeyedArchiver = YES;
}
}
// 设置class类型
meta->_cls = propertyInfo.cls;

// 如果是容器类型
if (generic) {
// 从容器class 中读取
meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
} else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
// 从class类型中读取
meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
}

// getter 和 setter 方法
if (propertyInfo.getter) {
if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
meta->_getter = propertyInfo.getter;
}
}
if (propertyInfo.setter) {
if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
meta->_setter = propertyInfo.setter;
}
}

/**
* 只有实现了getter和setter方法 才能实现归档
*/
if (meta->_getter && meta->_setter) {
/*
KVC中不支持的类型
long double
指针对象 SEL等
*/
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool:
case YYEncodingTypeInt8:
case YYEncodingTypeUInt8:
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16:
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32:
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64:
case YYEncodingTypeFloat:
case YYEncodingTypeDouble:
case YYEncodingTypeObject:
case YYEncodingTypeClass:
case YYEncodingTypeBlock:
case YYEncodingTypeStruct:
case YYEncodingTypeUnion: {
meta->_isKVCCompatible = YES;
} break;
default: break;
}
}

return meta;
}
@end

流程大概如下
1.如果是属性是对象,通过 YYClassGetNSType 来从propertyInfo获取 NSType

判断属性是否可以归档。除了 CGSize ,CGPoint,CGRect,CGAffineTransform,UIEdgeInsets,UIOffset 其他都不支持归档
是否需要对字典设置单独的 class 通过类方法 modelCustomClassForDictionary 来返回特定的 model 类
判断是否支持 setter,和getter 方法。
判断时候支持 KVC。只有 long double,指针对象, SEL不支持KVC

_YYModelMeta

_YYModelMeta 是对一个需要映射的 Model YYClassInfo进行了一些描述信息的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// YYModelMeta 对ClassInfo增加描述
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;

// json key 和 property Meta 的映射关系字典
NSDictionary *_mapper;

// 所有属性的propertyMeta
NSArray *_allPropertyMetas;

// 映射jsonkeyPath 的PropertyMetas
NSArray *_keyPathPropertyMetas;

// 映射多个jsonKey的propertyMeta
NSArray *_multiKeysPropertyMetas;
/// 需要映射的属性的总个数
NSUInteger _keyMappedCount;
/// Model对应的Foundation class类型
YYEncodingNSType _nsType;
// 是否实现了自定义的映射关系表
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
@implementation _YYModelMeta
- (instancetype)initWithClass:(Class)cls {
// 创建classInfo对象
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
// 1. 判断是否合法
if (!classInfo) return nil;
self = [super init];

// 2. 获得黑名单
NSSet *blacklist = nil;
if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
if (properties) {
blacklist = [NSSet setWithArray:properties];
}
}

// 3. 获得白名单
NSSet *whitelist = nil;
if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
if (properties) {
whitelist = [NSSet setWithArray:properties];
}
}

// 4. 获取容器属性中的映射关系字典
NSDictionary *genericMapper = nil;
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
// 判断类中是否实现了对应的modelContainerPropertyGenericClass方法
/* 例如
@{@"shadows" : [YYShadow class],
@"borders" : YYBorder.class,
@"attachments" : @"YYAttachment" };
*/
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
if (genericMapper) {
// 将字段名和对应的class存放在字典里
NSMutableDictionary *tmp = [NSMutableDictionary new];
[genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (![key isKindOfClass:[NSString class]]) return;
Class meta = object_getClass(obj);
if (!meta) return;
if (class_isMetaClass(meta)) {
tmp[key] = obj;
} else if ([obj isKindOfClass:[NSString class]]) {
Class cls = NSClassFromString(obj);
if (cls) {
tmp[key] = cls;
}
}
}];
genericMapper = tmp;
}
}

// 5. 创建Class中 所有属性的PropertyMeta对象 加入到字典中
// 用来保存class 和其父类的所有属性 除了NSOject外
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
YYClassInfo *curClassInfo = classInfo;
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
// 遍历当前ClassInfo 中的所有PropertyInfo, 将它们封装成PropertyMeta
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;

// 通过propetyInfo来创建一个meta对象
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
// meta nanme必须非空
if (!meta || !meta->_name) continue;
// 必须实现get方法和set方法
if (!meta->_getter || !meta->_setter) continue;
// 字典中没有这个字段 避免重复操作
if (allPropertyMetas[meta->_name]) continue;
allPropertyMetas[meta->_name] = meta;
}
// 遍历父类的property
curClassInfo = curClassInfo.superClassInfo;
}
// 判断是否为空,不为空赋值给model声明中的_allPropertyMetas
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;

// 创建映射关系 jsonkey :propertyMeta
NSMutableDictionary *mapper = [NSMutableDictionary new];
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];

// 是否实现自定义的映射表
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
// 获得自定义的映射表
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];

// 遍历自定义的字典
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
// 创建propetyMeta
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
// 由于用户自定义映射,把原来映射的规则删除
[allPropertyMetas removeObjectForKey:propertyName];

if ([mappedToKey isKindOfClass:[NSString class]]) { // 判断key字段是否为非空NSString
if (mappedToKey.length == 0) return;
// 直接保存property映射的key
propertyMeta->_mappedToKey = mappedToKey;
// 如果是keyPath的情况, 用数组来处理
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];

// {@"name":@"user.name"} => name : @[@"user",@"name"]
if (keyPath.count > 1) {
// 保存keyPath映射关系
propertyMeta->_mappedToKeyPath = keyPath;
// 添加到keyPathPropertyMetas数组中
[keyPathPropertyMetas addObject:propertyMeta];
}

// 多个属性的时候,用next指针来指向前一个jsonKey映射的meta
propertyMeta->_next = mapper[mappedToKey] ?: nil;
// 保存jsonKey映射到最新的meta对象
mapper[mappedToKey] = propertyMeta;

} else if ([mappedToKey isKindOfClass:[NSArray class]]) { // 如果是数组 属于一个属性映射多个jsonKey

NSMutableArray *mappedToKeyArray = [NSMutableArray new];
for (NSString *oneKey in ((NSArray *)mappedToKey)) {
if (![oneKey isKindOfClass:[NSString class]]) continue;
if (oneKey.length == 0) continue;

NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
if (keyPath.count > 1) {
[mappedToKeyArray addObject:keyPath];
} else {
[mappedToKeyArray addObject:oneKey];
}

if (!propertyMeta->_mappedToKey) {
propertyMeta->_mappedToKey = oneKey;
propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
}
}
if (!propertyMeta->_mappedToKey) return;

propertyMeta->_mappedToKeyArray = mappedToKeyArray;
[multiKeysPropertyMetas addObject:propertyMeta];

propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
}
}];
}

// 处理没有自定义映射规则的属性
// 在上面的处理中 从allPropertyMetas中删除了有自定义映射规则的meta
// 剩下来的是没有自定义规则的属性,在这里就让这些属性的mappedKey等于属性名
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
// 直接让mappedKey等于属性名
propertyMeta->_mappedToKey = name;
propertyMeta->_next = mapper[name] ?: nil;
mapper[name] = propertyMeta;
}];

// 对映射的数据做修正处理
if (mapper.count) _mapper = mapper;
if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;

_classInfo = classInfo;
_keyMappedCount = _allPropertyMetas.count;
_nsType = YYClassGetNSType(cls);
_hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
_hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
_hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
_hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);

return self;
}

流程是:

获取黑名单和白名单。
创建 allPropertyMetas ,将所有属性添加到其中,根据黑,白名单剔除其中的元素。
将 allPropertyMetas 中的处理 keyPath 的 key 添加到 _keyPathPropertyMetas,将需要处理多 key 映射的添加到 _multiKeysPropertyMetas 中。
缓存机制
对于处理好的映射关系,YYModel 会对它们进行缓存,用的也是 CFDictionaryGetValue 来完成,使用 dispatch_semaphore_t 来保证线程安全

应用

简单的 Model 与 JSON 相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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,则转换过过程会使用其中任意一个不为空的值。

Model 包含其他 Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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

容器类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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

黑名单与白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface User
@property NSString *name;
@property NSUInteger age;
@end

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

数据校验与自定义转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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

Coding/Copying/hash/equal/description

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

安装

CocoaPods

在 Podfile 中添加 pod 'YYModel'。
执行 pod install。
导入 <YYModel/YYModel.h>。

例子

http://www.jianshu.com/p/b6aaf5d56257

比较YYModel和MJExtension:
YYModel、MJExtension 都是采用 Category 方式来实现功能,比较灵活,无侵入。
但注意 MJExtension 为 NSObject/NSString 添加了一些没有前缀的方法,且方法命名比较通用,可能会和一个工程内的其他类有冲突。

YYModel 会进行对象类型检查,避免将错误的对象类型赋值到属性,以避免潜在的 Crash 问题。YYModel 会尝试自动转换,

MJExtension 会对部分对象进行自动转换(比如 NSString 和 NSNumber 之间的转换),但当自动转换不能完成时,它会直接把 JSON 对象赋值给类型不匹配的 Model 属性。这样的结果会导致稍后 Model 在使用时,造成潜在的 Crash 风险。

参考:
iOS JSON 模型转换库评测http://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/

文章目录
  1. 1. 分析源码
    1. 1.1. YYClassInfo
      1. 1.1.1. YYClassIvarInfo
    2. 1.2. NSObject+YYModel
      1. 1.2.1. 对映射处理的封装
        1. 1.2.1.1. _YYModelPropertyMeta
        2. 1.2.1.2. _YYModelMeta
  2. 2. 应用
    1. 2.1. 简单的 Model 与 JSON 相互转换
    2. 2.2. Model 属性名和 JSON 中的 Key 不相同
    3. 2.3. Model 包含其他 Model
    4. 2.4. 容器类属性
    5. 2.5. 黑名单与白名单
    6. 2.6. 数据校验与自定义转换
    7. 2.7. Coding/Copying/hash/equal/description
    8. 2.8. 安装
  3. 3. 例子
本站总访问量 本站访客数人次 ,