YYModel实现原理

其实很早开始就想写这个了,因为自己一直在用这个库,而且它的代码量也比较少,另外作者ibireme写的代码质量很高。为了提高自己,拜读大神写的库是一个很好的方式。
本文将从头开始分析代码,所以文字可能会比较多。

YYClassMethodInfo

这个类保存了 Method 的信息(其实后面好像也没怎么用到。。。)。 该类的属性及描述如下:

属性名字 描述
Method method 对应的Method
NSString *name 方法的名字
SEL sel 方法选择器
IMP imp 方法实现
NSString *typeEncoding 参数及返回值类型的编码
NSString *returnTypeEncoding 返回值类型的编码
NSArray<NSString *> *argumentTypeEncodings 参数类型的编码

想更多的了解类型编码可以去看Type Encodings这篇博客,主要是runtime加快消息的分发
你可以使用@encode将相应类型转换成内部表示的字符串,当然也有一大部分内部使用的类型编码无法用@encode()返回。

YYClassPropertyInfo

这个类保存了 Property 信息,主要用到等有settergetter方法,成员变量名称,编码方式(很重要,根据这个来确定属性类型以及其它修饰符)。该类的属性及描述如下:

属性名字 描述
objc_property_t property 对应的Property
NSString *name 名字
YYEncodingType type 类型编码的类型
NSString *typeEncoding 类型编码
NSString *ivarName 成员变量名称,加了个_前缀
Class cls 属性类型
NSArray<NSString *> *protocols 被包含着的协议,可能为空
SEL getter getter方法,不能为空
SEL setter setter方法,不能为空

属性转换成YYClassPropertyInfo的过程比较麻烦,比较复杂的地方在于需要通过解析属性的类型编码,来确定 YYClassPropertyInfo 的YYEncodingType type属性。

YYEncodingType 主要包含了以下几方面的信息:

  • YYEncodingTypeMask: 属性值的类型,例如c的基本数据类型,结构体,id类型,class类型
  • YYEncodingTypeQualifierMask:不知道怎么形容这个,在Type Encodings这篇博客里面说是内部使用的类型编码。例如:const,in,out
  • YYEncodingTypePropertyMask:属性关键词,例如:strong, weak, readonly

YYClassIvarInfo

这个类保存了 Ivar 的信息。貌似不会用到,这里就不展开讲了。

YYClassInfo

每一个类(包括元类)都有一个对应的 YYClassInfo 实例。非元类对应的实例创建之后会被保存在一个静态字典缓存 classCache 中,元类对应的实例保存在另一个静态字典缓存 metaCache 中,以Class -> YYClassInfo的映射关系保存在缓存中。如果缓存中没有,则重新创建一个。
创建时,首先会通过 runtime 的方法得到父类,是否是元类,元类,名字等基本信息,然后按顺序分别将它所有的的 Method,Property,Ivar 生成对应的 YYClassMethodInfo,YYClassPropertyInfo,YYClassIvarInfo实例,添加到 YYClassInfo 中。创建完成之后开始创建父类的 YYClassInfo 实例,一直到根类。

YYClassInfo 保存了 Class 的绝大部分信息,但是 objc 是一门动态的语言,可以在运行时添加方法,属性等信息,这也意味着 YYClassInfo 里面保存的信息可能不是最新的。所以当你对 Class 做了一些修改之后,你需要先获得该 Class 对应的 YYClassInfo实例,然后手动调用- (void)setNeedUpdate;来刷新保存在缓存中的 info 信息。

属性名称 描述
Class cls 对应类
Class superCls 对应类的父类
Class metaCls 对应类的元类
BOOL isMeta 对应类是否是元类
NSString *name 类名
YYClassInfo *superClassInfo 父类对应的 YYClassInfo 实例
NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos 所有成员变量信息
NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos 所有方法信息
NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos 所有属性信息

YYModel协议

如果默认的模型转换不能满足你的需求,那么你可以通过实现 YYModel 协议中的方法达到自定义键值转化的过程。下面,简单介绍一下它的几个方法:

  • + (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;

实现这个方法,你可以自定义mode property -> json key之间的映射关系,你可以定义一个属性映射多个 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.Rowling."
},
"ID" : 100010
}

model:
@interface YYBook : NSObject <YYModel>
@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

在上面的那个例子中,实现该方法后,字典中 n 对应于属性 name,p 对应于属性 page。。。


  • + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;

实现该方法,你可以自定义容器类属性的泛型。例如 NSArray 和 NSDictionary 类的属性,你可以通过该方法你定义它们的元素(数组) value(字典)的类型


  • + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;

通过该方法,你可以自定义 json/字典 在不同的情况转化成不同 Class 的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@class YYCircle, YYRectangle, YYLine;

@implementation YYShape

+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
if (dictionary[@"radius"] != nil) {
return [YYCircle class];
} else if (dictionary[@"width"] != nil) {
return [YYRectangle class];
} else if (dictionary[@"y2"] != nil) {
return [YYLine class];
} else {
return [self class];
}
}

@end

  • + (nullable NSArray<NSString *> *)modelPropertyBlacklist;

黑名单,数组中的元素代表的是属性的名字。如果这个方法实现了,那么数组中的属性则不会被赋值。


  • + (nullable NSArray<NSString *> *)modelPropertyWhitelist;

白名单,数组的元素代表的是属性的名字。如果这个方法实现,那么不在这个数组中的属性不会被赋值


  • - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;

在数据模型转换之前,对json字典进行修改。如果实现了这个方法,那么将使用修改后的字典进行mode转换


  • - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;

如果你想自定义数据模型转换,那么你可以实现这个方法。在这个方法里你可以生成 mode 的实例,并根据 dic 对实例的属性赋值,记得创建成功后返回 YES

  • - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;

fixme 不知道干嘛

_YYModelMeta

这是一个内部类,它主要用来保存类的信息,每一个类都有一个对应的 _YYModelMeta 实例。创建好的实例会被保存在一个静态字典缓存中 cache,以clss -> _YYModelMeta的映射关系存储。如果缓存中没有该实例,则重新创建一个。看到这你是不是会觉得很像之前提到的YYClassInfo?简单点说,YYClassInfo 全盘记录了 class 的信息,_YYModelMeta 是对这些信息进行了加工整理。

下面是 _YYModelMeta 的构造函数,代码比较多,可以直接跳到后面看分析:

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
- (instancetype)initWithClass:(Class)cls {
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
if (!classInfo) return nil;
self = [super init];

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

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

// 自定义容器类属性的泛型
NSDictionary *genericMapper = nil;
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
if (genericMapper) {
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;
}
}

// 创建所有的元属性(不包括根类NSObject和NSProxy等属性)
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;

NSMutableDictionary *mapper = [NSMutableDictionary new];
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];

// 得到自定义数据字典key到mode属性之间的映射关系
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
[allPropertyMetas removeObjectForKey:propertyName];

if ([mappedToKey isKindOfClass:[NSString class]]) {
if (mappedToKey.length == 0) return;

propertyMeta->_mappedToKey = mappedToKey;
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
for (NSString *onePath in keyPath) {
if (onePath.length == 0) {
NSMutableArray *tmp = keyPath.mutableCopy;
[tmp removeObject:@""];
keyPath = tmp;
break;
}
}
if (keyPath.count > 1) {
propertyMeta->_mappedToKeyPath = keyPath;
[keyPathPropertyMetas addObject:propertyMeta];
}
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;

} else if ([mappedToKey isKindOfClass:[NSArray class]]) {

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 enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
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;
}
  1. 创建一个 YYClassInfo 的实例 classInfo
  2. 如果实现了协议的方法modelPropertyBlacklist,则得到黑名单列表 blacklist
  3. 如果实现了协议的方法modelPropertyWhitelist,则得到白名单列表 whitelist
  4. 如果实现了协议的方法modelContainerPropertyGenericClass,则得到容器类的泛型 genericMapper,映射关系为property -> class
  5. 创建该类以及父类(不包括根类)所有的元属性 _YYModelPropertyMeta
    • 遍历第一步中得到的 classInfo 的 propertyInfos 属性
    • 如果 blacklist 不为空,并且该属性的名字在里面,则 continue
    • 如果 whitelist 不为空,且该属性的名字不在里面,则 continue
    • 创建 _YYModelPropertyMeta 实例 meta,将 YYClassPropertyInfo 信息赋值给 meta,并添加到字典 allPropertyMetas 中,映射关系为property name ->_YYModelPropertyMeta
  6. 创建字典 mapper,其映射关系为mappedToKey -> _YYModelPropertyMeta,mappedToKey 即json字典中的key。
    • 如果实现了协议的方法modelCustomPropertyMapper,得到字典 customMapper,映射关系property name -> json key。为了方便我们把 key 叫做 propertyName, value 叫做 mappedToKey
    • 遍历 customMapper。根据 propertyName 在 allPropertyMetas 中找到对应的 _YYModelPropertyMeta 实例 meta,如果 meta 不为空,则将其移出 allPropertyMetas
    • 如果 mappedToKey 是字符串类型,则将其赋值给 meta 的 _mappedToKey 属性。如果 mappedToKey 的格式使用的 keyPath(类似 @”json.key”),则将该 mappedToKey 使用 @”.” 分割,将分割后等数组赋值给 mata 的 _mappedToKeyPath 属性,并且将 meta 添加到 keyPathPropertyMetas 数组。最后以mappedToKey -> meta的映射将其添加到字典 mapper 中,
    • 如果 mappedToKey 是数组类型,则说明一个属性可能映射了多个json字典的key。此时需要遍历 mappedToKey,它的每一个元素为 oneKey,使用@”.”分割,来判断是否使用了keyPath,分割后得到数组 keyPath。如果 keyPath 的 count 大于1,则将数组 keyPath 添加到 mappedToKeyArray 数组,如果不是则将 oneKey 添加到 mappedToKeyArray,meta 的 _mappedToKey 取值于遍历时第一个 oneKey。当遍历结束,将 mappedToKeyArray 赋值给 meta 的 mappedToKeyArray。最后以mappedToKey -> meta的映射关系将其添加到字典 mapper,将 meta 添加到数组 multiKeysPropertyMetas 中
    • 遍历第五步中的 allPropertyMetas,为了方便我们将字典中的 key 称为 key,value 称为 meta。 将 key 赋值给对应 meta 的 _mappedToKey,并且以property name -> meta的映射添加到 mapper
    • 当一个json字典的key对应着多个属性时,你可以使用 _YYModelPropertyMeta 的 _next来处理 fixme
  7. 最后是为 _YYModelMeta 其它一些属性赋值

从这个函数中我们可以看出,_YYModelMeta 处理了好几种情况下的数据模型转换问题

  1. 当自定义了映射关系,一个属性对应多个 key 时,使用 _multiKeysPropertyMetas 来处理
  2. 当自定义了映射关系,一个 key 对应多个属性时,使用 _YYModelPropertyMeta 的 _next 来处理
  3. _mapper 中包含了 key 跟 属性之间的映射关系
  4. 默认情况下,key 即为属性名,此时使用 _allPropertyMetas 来处理
  5. 通过一些bool值来表明 Class 实现了 》的哪几个方法

通过下图的_YYModelPropertyMeta的属性说明,能帮助更好的理解这一点

属性 说明
_classInfo 对应YYClassInfo实例
_mapper 字典,映射关系:json字典key -> 属性。如果一个key对应多个属性时,_mapper数量会小于属性数量
_allPropertyMetas 所有的_YYModelPropertyMeta实例数组
_keyPathPropertyMetas 使用了keyPath映射的_YYModelPropertyMeta实例数组
_multiKeysPropertyMetas 被多个key映射的_YYModelPropertyMeta实例数组
_keyMappedCount 等同于_mapper的count
_nsType 是 Founddation 的什么类,可能不是
_hasCustomWillTransformFromDictionary 是否实现了协议方法modelCustomWillTransformFromDictionary
_hasCustomTransformFromDictionary 是否实现了协议方法modelCustomTransformFromDictionary
_hasCustomTransformToDictionary 是否实现了协议方法modelCustomTransformToDictionary
_hasCustomClassFromDictionary 是否实现了协议方法modelCustomClassForDictionary

数据模型转换

通过 _YYModelMeta 生成实例的 modelMeta ,我们可以知道json字典跟属性之间的对应关系。所以,接下来要做的就是数据模型之间转换了。

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
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;


_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
if (modelMeta->_keyMappedCount == 0) return NO;

if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}

ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);


if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
if (modelMeta->_multiKeysPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}

if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}

首先根据 modelMeta 的 _hasCustomClassFromDictionary 来判断是否自定义了mode的类型,如果是,则获得自定的 mode 类型cls,并根据cls得到对应的 _YYModelMeta 实例 modelMeta。根据 modelMeta 的 _hasCustomWillTransformFromDictionary 来判断是否在转换前json字典进行修改,如果修改了则使用修改后的字典来转换mode。随后生成一个结构体 context,用来存储转换时要用到的信息。

比较 modelMeta 的 _keyMappedCount 与 json字典的 count 之间的大小

  • _keyMappedCount 大于等于字典的 count,则首先遍历字典,对里面的键值对调用ModelSetWithDictionaryFunction方法。如果 modelMeta 的 _keyPathPropertyMetas 和 _multiKeysPropertyMetas 不为空,则对里面的每个元素调用ModelSetWithPropertyMetaArrayFunction方法
  • _keyMappedCount 小于字典的 count,则对 _allPropertyMetas 里面的每个元素调用ModelSetWithPropertyMetaArrayFunction方法

(应该是哪个少遍历哪个,减少开销。。。)


首先让我们看一下上面提到的第一个方法ModelSetWithDictionaryFunction

1
2
3
4
5
6
7
8
9
10
11
12
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
__unsafe_unretained id model = (__bridge id)(context->model);
while (propertyMeta) {
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}

这个方法比较简单,根据 key 在 meta 的 _mapper 中找到相应的 _YYModelPropertyMeta 实例,随后调用ModelSetValueForProperty方法为该属性赋值,这个方法稍后再提。因为存在一个 key 对应多个 属性的情况,所以对该属性赋值后,会沿着 _next 继续为其它对应的属性赋值。


然后再看一下第二个方法ModelSetWithPropertyMetaArrayFunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
if (!propertyMeta->_setter) return;
id value = nil;

if (propertyMeta->_mappedToKeyArray) {
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}

if (value) {
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}

这个方法里主要做了这么几件事情:

  • 找到json字典中的数据
  • 调用ModelSetValueForProperty方法将数据赋值给属性,是的,又是这个方法

如何取值

当调用 ModelSetWithPropertyMetaArrayFunction 方法时,传入了上下文 context(里面包含了我们要用到的json字典context->dictionary)和元属性_YYModelPropertyMeta *propertyMeta
接下来就是分不同的情况来取值了:

  1. 如果有多个key对应同一个属性时,那么会取第一个key且相应json字典中value不为空时的value。例如,有一个json @{@”name” : @”11”, @”title” : @”222”}, 如果有 @”age”, @”name”, @”title” 这几个 key 对应同一个属性 name,那么只会取json中 @”name” 对应的值
  2. 如果使用了 keyPath 来定义属性的映射,那么在json字典中会逐级获取数据(不知道怎么表达了。。。),例如有一个json @{@”info” : @{@”name” : @”111”}}, 并且使用@”info.name”来映射属性,那么首先会取得 @”info”对应的字典 dic,然后再在dic中取得@”name”的值
  3. 如果没有上述两种情况,则直接根据key在json字典中取值

好了,是不是看了感觉还挺简单的,复杂的其实在赋值这一步!

如何赋值

赋值的过程比较复杂,且代码量比较多, 这里就不贴出来了。在这里我简单的分析一下过程:


首先属性是基本数据类型

1
2
3
4
5
if (meta->_isCNumber) {
NSNumber *num = YYNSNumberCreateFromID(value);
ModelSetNumberToProperty(model, num, meta);
if (num) [num class]; // hold the number
}

这个比较简单。首先将从json字典得到的value进行处理,得到一个 NSNumber 类型的数据 num。然后将 num 转换成相应类型的数据,通过objc_msgSend消息发送赋值给该属性。由于在赋值的函数中参数的类型是__unsafe_unretained(类似weak),所以需要在赋值成功前持有该数据,否则程序会因为 num 成为野指针而崩溃,所以在ModelSetNumberToProperty后面还有这样一行代码if (num) [num class];,看似没用,其实还是有点用的。如果你想对__unsafe_unretained了解深一点可以看孙源的这篇博客


然后时属性属于 Foundation 类型时,会先将 value 转换成属性的类型meta->_nsType,然后通过objc_msgSend赋值给属性。
当属性属于数组(NSArray, NSMutableArray)和字典(NSDictionary, NSMutableDictionary)时复杂一点:

  • 数组
    • 如果没有指定泛型,那么直接把value复制给属性
    • 会遍历数组value,将元素转换成相应的泛型,然后添加到一个新数组value中,最后将该value赋值给属性
  • 字典
    • 如果没有指定泛型,那么直接把value复制给属性
    • 会遍历字典values,将value转化成相应的泛型,添加到一个新字典vlues中。最后将该values赋值给属性

属性属于其它的类型,例如自定义的 objc 类,block,c的结构体,联合,数组等,转换过程跟之前也是差不多的。

总结

写了好几天,终于完成了。希望大家看完后对这个库的使用能够有所帮助。在这里可算是帮我解决了个疑问:
在自定义mode中就算为容器类指定了泛型,但转换的时候还是会失败, 原因是我们不能在类型编码中得到泛型的信息…

引用:YYModel

作者

千行

发布于

2019-07-30

更新于

2022-10-21

许可协议

评论