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
orNSData
. 从网络上传过来什么可以直接用,很方便。 - 不需要调用系统函数将JSON转换为Dictionary,YYModel里面已经做了这一步了。
- 返回的实例
instancetype
有可能为nil
Model -》JSON
- (nullable id)yy_modelToJSONObject;
- 这是一个实例方法。实例已经存在,将实例转换为JSON对象,然后网络传输,符合使用场景。
- 返回值是id,动态类型;结果是一个JSON对象,类型可以是
NSDictionary
orNSArray
,符合JSON的定义(字典或者数组)。JSON 数据格式 - 返回的JSON对象有可能为
nil
实际的例子
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 '<YYModel>' to your class header.
*/
@protocol YYModel <NSObject>
@optional
@end
不需要加
<YYModel>
是因为实现者(代理)是Model自己。这是特殊的用法。
1. Model 属性名和 JSON 中的 Key 不相同
协议方法:
+ (nullable NSDictionary<NSString *, id> *)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<NSString *, id> *)modelContainerPropertyGenericClass;
Model定义:
@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
其他的特性比如黑白名单,转换后的校验,自定义字典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
结构。通过对比,可以看出FastEasyMapping
和JSONModel
“容错”性能比较差,如果后台数据返回错误,很容易崩溃。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)存类方法(+ 开头的方法)
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
- 起点:
+ (nullable instancetype)yy_modelWithJSON:(id)json;
- 将JSON对象转换为字典:
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json
这里用到了系统的JSON转字典API:
+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
- 字典转模型:
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
- 设置模型属性:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
- 利用函数
CFDictionaryApplyFunction
调用static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context);
或者,利用函数CFArrayApplyFunction
调用static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context);
- 上面两个函数,都调用全局函数:
static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta);
所有的实际工作都在这里
Model -》JSON
- API函数:
- (nullable id)yy_modelToJSONObject;
id的类型是
NSDictionary
orNSArray
,根据实际情况指定
- 如果需要
NSDictionary
orNSArray
的结果,那么调用另外两个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_msgSend
的SEL
参数基本上都是meta->_setter
case1:数字类型
- 实现过程在全局函数中
ModelSetNumberToProperty
- 细分为布尔型,8位,16位,32位,64位整数,浮点数等等各种具体类型
case2:Foundation类型
-
Model属性类型是
NSString
或者NSMutableString
;对value(id)
的具体类型做了容错处理:
** NSString:直接设置
** NSMutableString:转化为NSString
之后,调用mutableCopy
NSNumber:取属性stringValue
** NSData:**转化为NSString
NSURL:取属性absoluteString
NSAttributedString:取属性string
-
Model属性类型是
NSDecimalNumber
;对value(id)
的类型是NSDecimalNumber
、NSNumber
、NSString
的情况做了容错处理 -
Model属性类型是
NSData
;对value(id)
的类型是NSString
的情况做了容错处理 -
Model属性类型是
NSDate
;对value(id)
的类型是NSString
的情况做了容错处理 -
Model属性类型是
NSURL
;对value(id)
的类型是NSString
的情况做了容错处理 -
Model属性类型是
NSDictionary
、NSSet
、NSArray
等容器类型时,对容器中每个成员调用函数yy_modelSetWithDictionary
,一层层深入下去。
case3:其他类型
都对value为nil的情况做了处理
-
Model属性类型是
id
对象类型时,调用函数yy_modelSetWithDictionary
,一层层深入下去。 -
Model属性类型是
Class
类型时,如果value是NSString
,则调用函数NSClassFromString
进行转化,然后设置。如果是其他类型,则判断其“元类”class_isMetaClass
是否存在。存在,则直接设置 -
Model属性类型是
SEL
类型时,如果value是NSString
,则调用函数NSSelectorFromString
进行转化,然后设置。 -
Model属性类型是
Block
类型时,将value强制转换为void (^)()
进行设置。 -
Model属性类型是
struct
、union
、char[10]
等类型时,如果value是NSValue
,则调用函数- (void)setValue:(nullable id)value forKey:(NSString *)key;
进行设置。 -
Model属性类型是
void*
、char*
等类型时,如果value是NSValue
,则将value强制转换为NSValue
,取属性pointerValue
进行设置。进行设置。
个人意见
- 看上去只有两个文件,但是类有很多,用了很多的内部类。这种方式不是很认同。还是推荐一个类一个源文件的方式。当然,这里的场景是高内聚的一个整体,本来也是把一个类的各子成员(都是
struct
),还是比较合适的。 - 将所有的头文件都归总为一个
YYMode.h
,这种方式是非常好的,推荐使用。 - 对于NSArray这种集合提供方便方法;对于JSON对象,采用id类型,支持NSDictionary, NSString, NSData三种类型;在处理的时候,统一为NSDictionary。这种方式,统筹考虑了实现和使用的方便性,只是增加了几层函数调用,值得推荐。
- 协议的定义、使用者、实现者都是同一个(self,Model自己),这里是特殊的使用场景。
一般情况下应该分3个文件(协议定义,使用者,实现者)或者2个文件(协议的定义放在使用者的文件中)。 - 采用协议的设计方式,让使用者对特殊使用场景做自定义,值得推荐
- 将_YYModelMeta(runtime中各种struct对应的类)作为转换的中间载体,思维很巧妙
-
if
后面只有一个语句,省略了{}
,这种习惯不是很好。
if (num) [num class]; // hold the number
参考文章
YYModel
iOS JSON 模型转换库评测
JSON 数据格式
Objective-C Runtime Programming Guide