前言
YYModel 是一個iOS JSON模型轉化庫,和其他一些同類型庫相比,具有比較好的性能優勢。本文會對YYModel的源碼進行分析,具體用法作者ibireme在github中有提及。YYModel的目錄結構很簡單,只有兩個類, NSObject+YYModel
和 YYClassInfo
。YYClassInfo
主要對根類NSObject 的 Ivar
, Method
, Property
以及Class
本身進行了封裝,NSObject+YYModel
是 NSObject的分類,擴展了一些JSON模型轉化的方法。下圖一張YYmodel的整體結構圖:
YYClassInfo
要點
YYClassIvarInfo : 對 Class的Ivar進行了封裝
YYClassMethodInfo : 對 Class的Method進行了封裝
YYClassPropertyInfo : 對 Class的Property進行了封裝
YYClassInfo : 對Class進行了封裝,包含了YYClassIvarInfo,YYClassMethodInfo,YYClassPropertyInfo
變量類型編碼
說到變量,可能我們寫代碼中最常見的就是變量了。當我們自定義一個類時,首先總是會申明一些屬性,而且每個屬性都會帶有一些修飾詞。比如是否原子性,內存管理原則,只讀性。這些都可以通過這個屬性的property_getAttributes
方法獲取,蘋果為所有的類型,包括屬性類型都有編碼,具體可以查看蘋果官方文檔:類型編碼, 蘋果官方文檔:屬性類型。 下面是一個簡單例子:
- 申明屬性
@interface Cat : NSObject
@end
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,strong) Cat *cat;
- (NSDictionary *)getProperties;
@end
2.通過runtime 來獲取所有屬性
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (NSDictionary *) getProperties{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (NSUInteger i = 0; i < count; i++) {
const char *name = property_getName(propertyList[i]);
const char *attribute = property_getAttributes(propertyList[i]);
NSString *nameStr = [NSString stringWithUTF8String:name];
NSString *attributeStr = [NSString stringWithUTF8String:attribute];
dic[nameStr] = attributeStr;
}
return [dic copy];
}
@end
3.打印結果
2017-01-03 10:54:34.690 Properties[16941:881462] {
age = "Tq,N,V_age";
cat = "T@\"Cat\",&,N,V_cat";
name = "T@\"NSString\",C,N,V_name";
}
我們可以看到屬性的所有類型編碼信息,其中第一個代表是這個變量的類型,以T開頭,最后一個代表的是變量的名字,一般用V_屬性名表示,中間的部分就是我們聲明的修飾符。比如age的類型是 Tq,而在官方文檔中q 代表了A long long
,64bit下NSInteger的取值范圍就是long == long long ,N代表了非原子性,變量名是_age。其他的@代表了OC類型 id
,cat類型即是T@"Cat",&代表了 這個變量是retain (ARC下strong相當于retain),C 代表了copy
YYEncodingType
根據類型編碼自定義了類型枚舉,包含了三個部分
YYEncodingTypeMask : 0~8位的值,變量的數據類型
YYEncodingTypeQualifierMask : 8~16位的值,變量的方法類型
YYEncodingTypePropertyMask: 16~24位的值,變量的屬性類型
這里把枚舉值分成三個部分,通過 枚舉值 & 對應 Mask 取出對應的變量類型,區分不同類型部分。YYEncodingGetType
是根據變量的數據類型編碼值獲取自定義YYEncodingType
Var Method Property
YYClassIvarInfo:用于存取變量的信息
/**
Instance variable information.
*/
@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
/**
Creates and returns an ivar info object.
@param ivar ivar opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithIvar:(Ivar)ivar;
@end
Ivar是表示實例變量的類型,其實際是一個指向objc_ivar結構體的指針,其定義如下:
struct objc_ivar {
charchar *ivar_name OBJC2_UNAVAILABLE;
charchar *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
運用runtime,name
通過的ivar_getName
獲取,offset
通過ivar_getOffset
獲取,typeEncoding
通過 ivar_getTypeEncoding
獲取,type
通過自定義方法 YYEncodingGetType
獲取。其中offset
是變量的基地址偏移量,可以通過它來直接訪問變量數據,下面是例子:
Person *p = [[Person alloc]init];
// NSLog(@"%@",[p getProperties]);
p.age = 20;
Ivar age_ivar = class_getInstanceVariable([Person class], "_age");
long *age_pointer = (long *)((__bridge void *)(p) + ivar_getOffset(age_ivar));
NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));
*age_pointer = 10;
NSLog(@"%@", p);
- (NSString *)description {
NSLog(@"current pointer = %p", self);
NSLog(@"age pointer = %p", &_age);
return [NSString stringWithFormat:@"age = %zi", _age];
}
打印結果:
2017-01-03 14:41:14.175 Properties[20934:1554728] age ivar offset = 16
2017-01-03 14:41:14.176 Properties[20934:1554728] current pointer = 0x600000075140
2017-01-03 14:41:14.176 Properties[20934:1554728] age pointer = 0x600000075150
2017-01-03 14:41:14.177 Properties[20934:1554728] age = 10
YYClassMethodInfo:用于存取方法的信息
/**
Method information.
*/
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; ///< method opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< method name
@property (nonatomic, assign, readonly) SEL sel; ///< method's selector
@property (nonatomic, assign, readonly) IMP imp; ///< method's implementation
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type
/**
Creates and returns a method info object.
@param method method opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithMethod:(Method)method;
@end
Method
的信息同 Ivar
一樣,通過runtime的 method相關方法獲取,其他的一些信息同Ivar
,主要來說一下 SEL
和 Imp
SEL: 方法ID,C字符串
typedef struct objc_selector *SEL;
/// Defines a method
struct objc_method_description {
SEL name; /**< The name of the method */
char *types; /**< The types of the method arguments */
};
IMP:方法函數指針
OC是動態語言,方法調用(也叫做消息發送)是在運行時動態綁定的,而非編譯時。如何做到正確的調用指定的方法呢?這里就需要用到SEL和IMP。編譯器會將消息發送轉換成對objc_msgSend(void /* id self, SEL op, ... */ )
方法的調用 。objc_msgSend
方法根據對象的isa指針找到對象的類,通過在類中的調度表(dispatch table)中查找SEL 獲得 IMP,精確執行指定方法。
YYClassPropertyInfo: 用于存取屬性的信息
/**
Property information.
*/
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< property's name
@property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value
@property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name
@property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil
@property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull)
@property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull)
/**
Creates and returns a property info object.
@param property property opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithProperty:(objc_property_t)property;
@end
YYClassInfo: 對Class的封裝,包含了上面三部分的信息
/**
Class information for a class.
*/
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
@property (nonatomic, strong, readonly) NSString *name; ///< class name
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
元類
其中有metaCls
代表了元類。那什么是元類呢?下面是一張經典的類結構圖
在OC中,每個實例對象都有一個isa指針,它指向了對象的class。而這個class也同樣有一個isa指針,它就是指向了它的元類,其實類也是一個對象,所以對象之于類的關系,就相當于類(類對象)之于其元類(類對象的類)的關系。那元類有什么用呢?我們都知道在OC中調用方法有實例方法和類方法。我們調用實例方法,就是通過isa指針找到指定的class,查找存儲在class中的方法列表執行方法,所以元類的作用就是調用類方法時,通過查找保存在元類中的類方法執行方法的作用。那為什么不把所有方法都保存在類中,可能這樣更加高效也節省資源吧,具體可以自己查找資料。
在YYClassInfo中,有一個_update
方法,用來更新類中存儲的信息。
初始化方法
+ (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);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls];
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);
}
}
return info;
}
要點:
CFDictionaryCreateMutable 和 CFDictionarySetValue 不是線程安全的,所以需要創建鎖,采用
dispatch_semaphore
通過控制信號量實現了鎖設置緩存,如果在緩存中存在class,則直接獲取到對應的ivar,method,property,否者創建YYClassInfo實例對象
NSObject+YYModel:
NSObject+YYModel
是YYModel的核心類,主要部分:
強制內聯C函數:功能函數
私有類_YYModelPropertyMeta : 管理Model屬性的數據, 類型, 映射的key,keyPath
私有類 _YYModelMeta :管理Model 數據,類型,存儲 映射key,keypath,_YYModelPropertyMeta
NSObject NSArray NSDictionary (YYModel) : 幾個分類,YYModel主體功能實現
YYModel 協議:擴展功能實現
私有類_YYModelPropertyMeta
_YYModelPropertyMeta
是對上面的 YYClassPropertyInfo
的進一步封裝。
內部實例變量
/// A property info in object model.
// model property的進一步分裝
@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; //屬性名
YYEncodingType _type; //屬性的編碼類型
YYEncodingNSType _nsType; //屬性的Foundation類型
BOOL _isCNumber; //是否c語言的數字
Class _cls; //屬性的class
Class _genericCls; //屬性內包含的類class
SEL _getter; //屬性 getter方法
SEL _setter; //屬性 setter方法
BOOL _isKVCCompatible; //是否可以使用KVC
BOOL _isStructAvailableForKeyedArchiver; //是否struct并且可以歸檔
BOOL _hasCustomClassFromDictionary; //是否包含本本地的class轉換
/*
property->key:
_mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil
property->keyPath:
_mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
property->keys:
_mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array)
*/
NSString *_mappedToKey; //屬性名映射的 key
NSArray *_mappedToKeyPath; //屬性名映射的 keyPath
NSArray *_mappedToKeyArray; //屬性名的映射的key keyPath 數組
YYClassPropertyInfo *_info; //屬性的YYClassPropertyInfo info
_YYModelPropertyMeta *_next;
//多個屬性名映射到同一個key 時,指向下一個屬性名的YYModelPropertyMeta 指針
}
@end
主要是最后幾個變量。其中_mappedToKey
_mappedToKeyPath
_mappedToKeyArray
是屬性映射的key,keyPath ,key (keypath) 數組_mappedToKeyArray
中可以是key和keyPath,實際取其中第一個映射到的值
一般一個屬性名對應一個key值,如果多個屬性名對應同一個key,這里就需要next發揮作用了。比如
{
@"name1" : @"name",
@"name2" : @"name",
@"name3" : @"name",
}
首先name1最先得到映射,對mapKey進行賦值,取得json中的name字段進行賦值一系列操作,此時next指針為nil
name2接著進行映射,對mapKey進行賦值,接著取得原來json key對應的屬性描述對象,將name2的next指針,指向name1。
name3接著進行映射,對mapKey進行賦值,接著取得原來json key對應的屬性描述對象,將name3的next指針,指向name2。
代碼中的實現
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
初始化方法
@implementation _YYModelPropertyMeta
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
//創建并且根據propertyInfo 進行變量賦值
_YYModelPropertyMeta *meta = [self new];
meta->_name = propertyInfo.name;
meta->_type = propertyInfo.type;
meta->_info = propertyInfo;
// 屬性為容器類型的時候, 映射類型賦值
meta->_genericCls = generic;
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
// 是否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
*/
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
私有類_YYModelMeta
_YYModelMeta
是對 YYClassInfo
的再次封裝
內部變量
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;
// key: 映射的 json key ,keyPath value: _YYModelPropertyMeta
NSDictionary *_mapper;
// model所有屬性的PropertyMetas
NSArray *_allPropertyMetas;
//model所有映射son keyPath 屬性 的PropertyMetas
NSArray *_keyPathPropertyMetas;
// model所有映射多個key 屬性 的PropertyMetas
NSArray *_multiKeysPropertyMetas;
/// 需要映射的屬性總個數
NSUInteger _keyMappedCount;
/// Model對應的Foundation 類型
YYEncodingNSType _nsType;
// 事否實現了自定義的映射關系表
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
初始化
/// Returns the cached model class meta
+ (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;
}
首先從緩存中加載,沒有在根據傳入cls 創建meta,并做緩存處理, dispatch_semaphore 確保線程安全
@implementation _YYModelMeta
- (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;
// 判斷類中是否實現了對應的modelContainerPropertyGenericClass方法
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
// 存儲key和對應的class到字典中
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;
}
}
// 存儲所有屬性的PropertyMeta對象
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來創建PropertyMeta 對象
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
// meta name非空
if (!meta || !meta->_name) continue;
// 需要實現get方法和set方法
if (!meta->_getter || !meta->_setter) continue;
// 字典中已有該字段的meta 避免重復操作
if (allPropertyMetas[meta->_name]) continue;
allPropertyMetas[meta->_name] = meta;
}
// 遍歷父類的property
curClassInfo = curClassInfo.superClassInfo;
}
if (allPropertyMetas.count)
_allPropertyMetas = allPropertyMetas.allValues.copy;
// 創建 key :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) {
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
// 由于用戶自定義映射,把原來映射的數據刪除
[allPropertyMetas removeObjectForKey:propertyName];
if ([mappedToKey isKindOfClass:[NSString class]]) {
// key字段非空
if (mappedToKey.length == 0) return;
// 保存映射的key
propertyMeta->_mappedToKey = mappedToKey;
// 如果是keyPath的情況處理
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
if (keyPath.count > 1) {
propertyMeta->_mappedToKeyPath = keyPath;
[keyPathPropertyMetas addObject:propertyMeta];
}
// 多個屬性映射同一個key的時候,用next存儲前一個 json Key映射的meta
propertyMeta->_next = mapper[mappedToKey] ?: nil;
// 保存新的meta對象
mapper[mappedToKey] = propertyMeta;
} else if ([mappedToKey isKindOfClass:[NSArray class]]) {
// 一個屬性映射多個json Key
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) {
// 直接讓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;
}
JSON 轉 Model
+ (instancetype)yy_modelWithJSON:(id)son {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
return [self yy_modelWithDictionary:dic];
}
傳入的json可以是 NSDictionary
, NSString
, NSData
,_yy_dictionaryWithJSON
統一轉化成字典
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
//字典合法性校驗
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
Class cls = [self class];
//創建 model 元數據
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
//自定義字典
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 根據dictionary 進行 model 屬性賦值
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
結構體 ModelSetContext
存儲 modelMeta ,model, dic 作為 CFDictionaryApplyFunction
和 CFArrayApplyFunction
的context 參數,傳遞數據
- (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)) {
//映射的key Count >= dic Count
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 {
//映射的key Count < dic Count
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
調用CoreFoundation 的 CFDictionaryApplyFunction 和 CFArrayApplyFunction 回調自定義的 Apply function
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context)
和 static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context)
根據獲得的value ,model ,_propertyMeta 最后統一調用 下面方法,運用runtime的 objc_msgSend
設置model屬性
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)
自定義的CFDictionaryApplyFunction
的回調方法,CoreFoundation中的原回調函數
typedef void (*CFDictionaryApplierFunction)(const void *key, const void *value, void *context);
/**
Apply function for dictionary, to set the key-value pair to model.
@param _key should not be nil, NSString.
@param _value should not be nil.
@param _context _context.modelMeta and _context.model should not be nil.
*/
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;
};
}
自定義的CFArrayApplyFunction
的回調方法,CoreFoundation中的原回調函數
typedef void (*CFArrayApplierFunction)(const void *value, void *context);
/**
Apply function for model property meta, to set dictionary to model.
@param _propertyMeta should not be nil, _YYModelPropertyMeta.
@param _context _context.model and _context.dictionary should not be nil.
*/
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) {
//從dic中獲取映射多個key的值,返回映射到的第一個值
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
//從dic中獲取映射keypath的值
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
//從dic中獲取映射key的值
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}
if (value) {
//Model 屬性賦值
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}
最終實現model 屬性賦值。 該方法比較長,首先對 meta的屬性類型進行判斷,主要分為三類,
- C基本數據類型
- Foundation 類型
- 其他類型,如 id, Class ,block,SEL等等
根據類型獲取對應value,
最后都調用 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value)
進行model 屬性賦值
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)
Model 轉 JSON
其中有效的JSON Object 只能是以下類型:
NSArray,NSDictionary,NSString,NSNumber,NSNull。
1.如果是NSDictionary,NSSet,NSArray 類型,遞歸調用此方法獲得JSON Object
2.如果是NSURL,NSAttributedString ,NSDate, NSData,做簡單相應返回
3.根據Model類型創建modelMeta,取實例變量_mapper
獲取所有屬性名和`propertyMeta
,再根據propertyMeta
的 類型_type
獲得相應的value
, 根據有無_mappedToKeyPath
再進一步處理,賦值,最后 判斷有無自定義_hasCustomTransformToDictionary
,返回最終轉化結果
主要方法:
static id ModelToJSONObjectRecursive(NSObject *model) {
if (!model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;
if ([model isKindOfClass:[NSDictionary class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableDictionary *newDic = [NSMutableDictionary new];
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
if (!stringKey) return;
id jsonObj = ModelToJSONObjectRecursive(obj);
if (!jsonObj) jsonObj = (id)kCFNull;
newDic[stringKey] = jsonObj;
}];
return newDic;
}
if ([model isKindOfClass:[NSSet class]]) {
NSArray *array = ((NSSet *)model).allObjects;
if ([NSJSONSerialization isValidJSONObject:array]) return array;
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in array) {
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
}
if ([model isKindOfClass:[NSArray class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in (NSArray *)model) {
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
}
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
if ([model isKindOfClass:[NSData class]]) return nil;
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
__unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
if (!propertyMeta->_getter) return;
id value = nil;
if (propertyMeta->_isCNumber) {
value = ModelCreateNumberFromProperty(model, propertyMeta);
} else if (propertyMeta->_nsType) {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
} else {
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
if (!value) return;
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
if (i + 1 == max) { // end
if (!superDic[key]) superDic[key] = value;
break;
}
subDic = superDic[key];
if (subDic) {
if ([subDic isKindOfClass:[NSDictionary class]]) {
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break;
}
} else {
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
superDic = subDic;
subDic = nil;
}
} else {
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
}];
if (modelMeta->_hasCustomTransformToDictionary) {
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
if (!suc) return nil;
}
return result;
}