官方文档: The CFNull opaque type defines a unique object used to represent null values in collection objects (which don’t allow NULL values). CFNull objects are neither created nor destroyed. Instead, a single CFNull constant object—kCFNull—is defined and is used wherever a null value is needed.
官方注释: The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
NSDictionary to Model
现在我们要从 yy_modelWithJSON 接口中探究 yy_modelWithDictionary 是如何将 NSDictionary 转为 Model 的。
// 递归转换模型到 JSON,如果转换异常则返回 nil staticid ModelToJSONObjectRecursive(NSObject *model) { // 判空或者可以直接返回的对象,则直接返回 if (!model || model == (id)kCFNull) return model; if ([model isKindOfClass:[NSStringclass]]) return model; if ([model isKindOfClass:[NSNumberclass]]) return model; // 如果 model 从属于 NSDictionary if ([model isKindOfClass:[NSDictionaryclass]]) { // 如果可以直接转换为 JSON 数据,则返回 if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableDictionary *newDic = [NSMutableDictionary new]; // 遍历 model 的 key 和 value [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { NSString *stringKey = [key isKindOfClass:[NSStringclass]] ? key : key.description; if (!stringKey) return; // 递归解析 value id jsonObj = ModelToJSONObjectRecursive(obj); if (!jsonObj) jsonObj = (id)kCFNull; newDic[stringKey] = jsonObj; }]; return newDic; } // 如果 model 从属于 NSSet if ([model isKindOfClass:[NSSetclass]]) { // 如果能够直接转换 JSON 对象,则直接返回 // 否则遍历,按需要递归解析 ... } if ([model isKindOfClass:[NSArrayclass]]) { // 如果能够直接转换 JSON 对象,则直接返回 // 否则遍历,按需要递归解析 ... } // 对 NSURL, NSAttributedString, NSDate, NSData 做相应处理 if ([model isKindOfClass:[NSURLclass]]) return ((NSURL *)model).absoluteString; if ([model isKindOfClass:[NSAttributedStringclass]]) return ((NSAttributedString *)model).string; if ([model isKindOfClass:[NSDateclass]]) return [YYISODateFormatter() stringFromDate:(id)model]; if ([model isKindOfClass:[NSDataclass]]) returnnil; // 用 [model class] 初始化一个模型元 _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]]; // 如果映射表为空,则不做解析直接返回 nil if (!modelMeta || modelMeta->_keyMappedCount == 0) returnnil; // 性能优化细节,使用 __unsafe_unretained 来避免在下面遍历 block 中直接使用 result 指针造成的不必要 retain 与 release 开销 NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64]; __unsafe_unretainedNSMutableDictionary *dic = result; // 遍历模型元属性映射字典 [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { // 如果遍历当前属性元没有 getter 方法,跳过 if (!propertyMeta->_getter) return; id value = nil; // 如果属性元属于 CNumber,即其 type 是 int、float、double 之类的 if (propertyMeta->_isCNumber) { // 从属性中利用 getter 方法得到对应的值 value = ModelCreateNumberFromProperty(model, propertyMeta); } elseif (propertyMeta->_nsType) { // 属性元属于 nsType,即 NSString 之类 // 利用 getter 方法拿到 value id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); // 对拿到的 value 递归解析 value = ModelToJSONObjectRecursive(v); } else { // 根据属性元的 type 做相应处理 switch (propertyMeta->_type & YYEncodingTypeMask) { // id,需要递归解析,如果解析失败则返回 nil case YYEncodingTypeObject: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); if (value == (id)kCFNull) value = nil; } break; // Class,转 NSString,返回 Class 名称 case YYEncodingTypeClass: { Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = v ? NSStringFromClass(v) : nil; } break; // SEL,转 NSString,返回给定 SEL 的字符串表现形式 case YYEncodingTypeSEL: { SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = v ? NSStringFromSelector(v) : nil; } break; default: break; } } // 如果 value 还是没能解析,则跳过 if (!value) return; // 当前属性元是 KeyPath 映射,即 a.b.c 之类 if (propertyMeta->_mappedToKeyPath) { NSMutableDictionary *superDic = dic; NSMutableDictionary *subDic = nil; // _mappedToKeyPath 是 a.b.c 根据 '.' 拆分成的字符串数组,遍历 _mappedToKeyPath for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) { NSString *key = propertyMeta->_mappedToKeyPath[i]; // 遍历到结尾 if (i + 1 == max) { // 如果结尾的 key 为 nil,则使用 value 赋值 if (!superDic[key]) superDic[key] = value; break; } // 用 subDic 拿到当前 key 对应的值 subDic = superDic[key]; // 如果 subDic 存在 if (subDic) { // 如果 subDic 从属于 NSDictionary if ([subDic isKindOfClass:[NSDictionaryclass]]) { // 将 subDic 的 mutable 版本赋值给 superDic[key] subDic = subDic.mutableCopy; superDic[key] = subDic; } else { break; } } else { // 将 NSMutableDictionary 赋值给 superDic[key] // 注意这里使用 subDic 间接赋值是有原因的,原因就在下面 subDic = [NSMutableDictionary new]; superDic[key] = subDic; } // superDic 指向 subDic,这样在遍历 _mappedToKeyPath 时即可逐层解析 // 这就是上面先把 subDic 转为 NSMutableDictionary 的原因 superDic = subDic; subDic = nil; } } else { // 如果不是 KeyPath 则检测 dic[propertyMeta->_mappedToKey],如果为 nil 则赋值 value if (!dic[propertyMeta->_mappedToKey]) { dic[propertyMeta->_mappedToKey] = value; } } }]; // 忽略,对应 modelCustomTransformToDictionary 接口 if (modelMeta->_hasCustomTransformToDictionary) { // 用于在默认的 Model 转 JSON 过程不适合当前 Model 类型时提供自定义额外过程 // 也可以用这个方法来验证转换结果 BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic]; if (!suc) returnnil; } return result; }
额…代码还是有些长,不过相比于之前 JSON to Model 方向上由 yy_modelSetWithDictionary,ModelSetWithDictionaryFunction 和 ModelSetValueForProperty 三个方法构成的间接递归来说算是非常简单了,那么总结一下上面的代码逻辑。
判断入参,如果满足条件可以直接返回
如果 Model 从属于 NSType,则根据不同的类型做逻辑处理
如果上面条件不被满足,则用 Model 的 Class 初始化一个模型元 _YYModelMeta
判断模型元的映射关系,遍历映射表拿到对应键值对并存入字典中并返回
Note: 这里有一个性能优化的细节,用 __unsafe_unretained 修饰的 dic 指向我们最后要 return 的 NSMutableDictionary *result,看作者的注释:// avoid retain and release in block 是为了避免直接使用 result 在后面遍历映射表的代码块中不必要的 retain 和 release 操作以节省开销。
官方注释: Registers a method with the Objective-C runtime system, maps the method name to a selector, and returns the selector value.
所以我觉得 SEL 和 char * 的的确确是有某种一一对应的映射关系,不过 SEL 的本质是否是 char * 就要打一个问号了。因为我在调试 SEL 阶段发现 SEL 内还有一个当前 SEL 的指针,与 char * 不同的是当 char * 赋值之后当前 char * 变量指针指向字符串首字符,而 SEL 则是 ,即我们无法直接看到它。
所以我做了一个无聊的测试,用相同的字符串初始化一个 char * 实例与一个 SEL 实例,之后尝试打印它们,有趣的是不论我使用 %s 还是 %c 都可以从两个实例中得到相同的打印输出,不知道鱼神是否做过相同的测试(笑~)
嘛~ 经过验证我们可以肯定 SEL 和 char * 存在某种映射关系,可以相互转换。同时猜测 SEL 本质上就是 char *,如果有哪位知道 SEL 与 char * 确切关系的可以留言讨论哟。
关于 Version 的官方描述: Classes derived from the Foundation framework NSObject class can set the class-definition version number using the setVersion: class method, which is implemented using the class_setVersion function.