YYModel介紹
YYModel是一個針對iOS/OSX平臺的高性能的Model解析庫,是屬于YYKit的一個組件背零,創(chuàng)建是ibireme迎吵。
其實在YYModel出現(xiàn)之前,已經(jīng)有非常多的Model解析庫桑逝,例如JSONModel、Mantle和MJExtension俏让。
YYModel從易用性和性能方面均達到了最高水平楞遏。
性能
特性
- High performance: The conversion performance is close to handwriting code.
- Automatic type conversion: The object types can be automatically converted.
- Type Safe: All data types will be verified to ensure type-safe during the conversion process.
- Non-intrusive: There is no need to make the model class inherit from other base class.
- Lightwight: This library contains only 5 files.
- Docs and unit testing: 100% docs coverage, 99.6% code coverage.
YYModel使用
簡單Model和JSON轉(zhuǎn)換
// 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
// Convert json to model:
User *user = [User yy_modelWithJSON:json];
// Convert model to json:
NSDictionary *json = [user yy_modelToJSONObject];
內(nèi)嵌Model
// JSON
{
"author":{
"name":"J.K.Rowling",
"birthday":"1965-07-31T00:00:00+0000"
},
"name":"Harry Potter",
"pages":256
}
// Model: (no need to do anything)
@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;
@end
@implementation Book
@end
集合類型 - Array、Set
@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
+ (NSDictionary *)modelContainerPropertyGenericClass {
// value should be Class or Class name.
return @{@"shadows" : [Shadow class],
@"borders" : Border.class,
@"attachments" : @"Attachment" };
}
@end
YYModel代碼結(jié)構(gòu)
YYModel整個項目非常簡潔首昔,只有5個文件寡喝。
文件 | 描述 |
---|---|
NSObject+YYModel | YYModel對于NSObject的擴展 |
YYClassInfo | 類信息 |
YYModel.h | YYModel的頭文件 |
詳細分析
以一個例子來分析,外部是Book對象勒奇,內(nèi)部有一個Author對象预鬓。
NSString *json = @"{ \
\"author\":{ \
\"name\":\"J.K.Rowling\", \
\"birthday\":\"1965-07-31T00:00:00+0000\" \
}, \
\"name\":\"Harry Potter\", \
\"pages\":256 \
}";
Book *book = [Book yy_modelWithJSON:json];
yy_modelWithJSON
入口從[NSObject yy_modelWithJSON]進入
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
return [self yy_modelWithDictionary:dic];
}
_yy_dictionaryWithJSON:將JSON的數(shù)據(jù)(String或者NSData)轉(zhuǎn)換成NSDictionary,主要使用系統(tǒng)方法[NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
yy_modelWithDictionary
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
...
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
modelCustomClassForDictionary - Model類可以重載這個方法赊颠,將JSON轉(zhuǎn)換成另外一個Model類
后續(xù)處理都放在了yy_modelSetWithDictionary這個方法
yy_modelSetWithDictionary
首先根據(jù)Class信息構(gòu)造出_YYModelMeta
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
_YYModelMeta中包含如下屬性:
- YYClassInfo *_classInfo:類信息格二,例如class、superclass竣蹦、ivarInfo顶猜、methodInfos、propertyInfos
- NSDictionary *_mapper:屬性key和對應的_YYModelPropertyMeta
{
author = "<_YYModelPropertyMeta: 0x6080000f5c00>";
name = "<_YYModelPropertyMeta: 0x6080000f5b00>";
pages = "<_YYModelPropertyMeta: 0x6080000f5b80>";
}
看下Name里面對應的_YYModelPropertyMeta的內(nèi)容:
* _name: 對應的是property的名字
* _nsType:對應property的類型
* _getter:getter方法
* _setter:setter方法
- NSArray *_allPropertyMetas:所有的_YYModelPropertyMeta
- NSArray *_keyPathPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to a key path
- NSArray *_multiKeysPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
數(shù)據(jù)填充
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);
}
CFDictionaryApplyFunction/CFArrayApplyFunction:針對NSDictionary和NSArray的每一個值草添,執(zhí)行一個方法驶兜。Context作為方法中一個參數(shù)扼仲,帶入了Model的信息远寸。
Context數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
ModelSetWithDictionaryFunction
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;
};
}
這個方法是將Dictionary的數(shù)據(jù)填充到Model的核心過程。
通過Context獲取meta(Model的類信息)屠凶,通過meta獲取當前Key的propertyMeta(屬性信息)驰后,遞歸調(diào)用ModealSetValueForProperty填充model里面對應Key的Property。
ModelSetValueForProperty
這個方法會將數(shù)據(jù)填充到Model對應的Property中矗愧。
對于普通數(shù)據(jù)類型的數(shù)據(jù)填充灶芝,大體如下:
switch (meta->_nsType) {
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
}
對于內(nèi)嵌的對象屬性郑原,處理如下:
Value通常是一個NSDicationary,如果有g(shù)etter方法夜涕,獲取這個property的對象犯犁,如果為nill則創(chuàng)建一個實例,再通過[one yy_modelSetWithDictionary:value]女器,填充這個property對象酸役。
case YYEncodingTypeObject: {
...
else if ([value isKindOfClass:[NSDictionary class]]) {
NSObject *one = nil;
if (meta->_getter) {
one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
}
if (one) {
[one yy_modelSetWithDictionary:value];
} else {
Class cls = meta->_cls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
}
}
} break;
最佳實踐
force_inline
在YYModel實現(xiàn)中大量使用force_inline關(guān)鍵詞來修飾方法,inline的作用可以參考Wikipedia: Inline Function驾胆。Inline Function會在編譯階段將方法實現(xiàn)直接拷貝到調(diào)用處涣澡,減少方法參數(shù)傳遞和查找,可以提高運行效率丧诺。
YYMode的使用方法如下:
#define force_inline __inline__ __attribute__((always_inline))
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
...
}
一次性初始化
對于一次性初始化的代碼盡量放在dispatch_once block中入桂,保證只會初始化一次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
Lock
通過Lock來保證多線程執(zhí)行的一致性
static dispatch_semaphore_t lock;
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// do something
dispatch_semaphore_signal(lock);
緩存的實現(xiàn)
通過CFDictionaryCreateMutable實現(xiàn)了一個簡易的文件緩存驳阎,注意在讀取和寫入緩存的時候都使用了Lock來保證多線程一致性抗愁。
+ (instancetype)metaWithClass:(Class)cls {
if (!cls) return nil;
static CFMutableDictionaryRef cache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
if (!meta || meta->_classInfo.needUpdate) {
meta = [[_YYModelMeta alloc] initWithClass:cls];
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
}
}
return meta;
}
總結(jié)
YYModel是一個非常簡潔、高性能的Model解析庫呵晚,作者使用了大量的runtime方式解析class內(nèi)部信息驹愚,使用了inline、緩存劣纲、Lock等方式提高了性能和安全性逢捺。
多讀經(jīng)典的開源庫,理解作者的實現(xiàn)方式癞季,對于提高iOS設計和編程能力有很大的幫助劫瞳。