YYModel 學習探究
YYModel是一款非常好用且非常輕量級的JSON模型轉換庫,原始碼一共就五個檔案,去掉宣告檔案,所有的實現邏輯都在NSObject+YYModel.m
和YYClassInfo.m
這兩個檔案中,如圖:
一、YYModel的程式碼結構
YYClassInfo功能主要是將Runtime層級中的一些api(結構體封)裝到NSObject子類中,將runtime的api物件化,方便呼叫;NSObject+YYModel是提供呼叫的介面以及實現具體的模型轉換邏輯,YYModel的程式碼結構如圖:
1、YYClassInfo中對runtime的Api封裝類
-
1.1、
YYClassIvarInfo
對objc_ivar
封裝對比-
YYClassIvarInfo
物件宣告:@interface YYClassIvarInfo : NSObject @property (nonatomic,assign,readonly) Ivar ivar; ///< ivar opaque struct @property (nonatomic,strong,readonly) NSString *name; ///< Ivar's name @property (nonatomic,readonly) ptrdiff_t offset; ///< Ivar'
- Runtime中
objc_ivar
的定義:struct objc_ivar { char * _Nullable ivar_name OBJC2_UNAVAILABLE; // 變數名稱 char * _Nullable ivar_type OBJC2_UNAVAILABLE; // 變數型別 int ivar_offset OBJC2_UNAVAILABLE; // 變數偏移量 #ifdef __LP64__ // 如果已定義 __LP64__ 則表示正在構建 64 位目標
-
-
1.2、
YYClassMethodInfo
對objc_method
封裝對比-
YYClassMethodInfo
物件宣告:@interface YYClassMethodInfo : NSObject @property (nonatomic,readonly) Method method; ///< 方法 @property (nonatomic,readonly) NSString *name; ///< 方法名稱 @property (nonatomic,readonly) SEL sel; ///< 方法選擇器 @property (nonatomic,readonly) IMP imp; ///< 方法實現,指向實現方法函式的函式指標 @property (nonatomic,readonly) NSString *typeEncoding; ///< 方法引數和返回型別編碼 @property (nonatomic,readonly) NSString *returnTypeEncoding; ///< 返回值型別編碼 @property (nullable,nonatomic,readonly) NSArray<nsstring *> *argumentTypeEncodings; ///< 引數型別編碼陣列 - (instancetype)initWithMethod:(Method)method; @end 複製程式碼
-
Runtime中
objc_method
的定義:struct objc_method { SEL _Nonnull method_name OBJC2_UNAVAILABLE; // 方法名稱 char * _Nullable method_types OBJC2_UNAVAILABLE; // 方法型別 IMP _Nonnull method_imp OBJC2_UNAVAILABLE; // 方法實現(函式指標) } 複製程式碼
-
-
1.3、
YYClassPropertyInfo
對property_t
封裝對比-
YYClassPropertyInfo
物件宣告:@interface YYClassPropertyInfo : NSObject @property (nonatomic,readonly) objc_property_t property; ///< 屬性 @property (nonatomic,readonly) NSString *name; ///< 屬性名稱 @property (nonatomic,readonly) YYEncodingType type; ///< 屬性型別 @property (nonatomic,readonly) NSString *typeEncoding; ///< 屬性型別編碼 @property (nonatomic,readonly) NSString *ivarName; ///< 變數名稱 @property (nullable,readonly) Class cls; ///< 型別 @property (nullable,readonly) NSArray<nsstring *> *protocols; ///< 屬性相關協議 @property (nonatomic,readonly) SEL getter; ///< getter 方法選擇器 @property (nonatomic,readonly) SEL setter; ///< setter 方法選擇器 - (instancetype)initWithProperty:(objc_property_t)property; 複製程式碼
-
Runtime中
property_t
的定義:struct property_t { const char *name; // 名稱 const char *attributes; // 修飾 }; 複製程式碼
-
-
1.4、
YYClassInfo
對objc_class
封裝對比-
YYClassInfo
物件宣告:@interface YYClassInfo : NSObject @property (nonatomic,readonly) Class cls; ///< 類 @property (nullable,readonly) Class superCls; ///< 超類 @property (nullable,readonly) Class metaCls; ///< 元類 @property (nonatomic,readonly) BOOL isMeta; ///< 元類標識,自身是否為元類 @property (nonatomic,readonly) NSString *name; ///< 類名稱 @property (nullable,readonly) YYClassInfo *superClassInfo; ///< 父類(超類)資訊 @property (nullable,readonly) NSDictionary<nsstring *,yyclassivarinfo *> *ivarInfos; ///< 變數資訊 @property (nullable,yyclassmethodinfo *> *methodInfos; ///< 方法資訊 @property (nullable,yyclasspropertyinfo *> *propertyInfos; ///< 屬性資訊 - (void)setNeedUpdate; - (BOOL)needUpdate; + (nullable instancetype)classInfoWithClass:(Class)cls; + (nullable instancetype)classInfoWithClassName:(NSString *)className; @end 複製程式碼
- Runtime中
objc_class
的定義:// objc.h typedef struct objc_class *Class; // runtime.h struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; // isa 指標 #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; // 父類(超類)指標 const char * _Nonnull name OBJC2_UNAVAILABLE; // 類名 long version OBJC2_UNAVAILABLE; // 版本 long info OBJC2_UNAVAILABLE; // 資訊 long instance_size OBJC2_UNAVAILABLE; // 初始尺寸 struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 變數列表 struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; // 方法列表 struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 快取 struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 協議列表 #endif } OBJC2_UNAVAILABLE; 複製程式碼
-
2、NSObject+YYModel 資料結構的定義
- NSObject+YYModel 資料結構的定義
-
_YYModelPropertyMeta
物件的宣告定義:@interface _YYModelPropertyMeta : NSObject { @package NSString *_name; ///< 屬性名稱 YYEncodingType _type; ///< 屬性型別 YYEncodingNSType _nsType; ///< 屬性在 Foundation 框架中的型別 BOOL _isCNumber; ///< 是否為 CNumber Class _cls; ///< 屬性類 Class _genericCls; ///< 屬性包含的泛型型別,沒有則為 nil SEL _getter; ///< getter SEL _setter; ///< setter BOOL _isKVCCompatible; ///< 如果可以使用 KVC 則返回 YES BOOL _isStructAvailableForKeyedArchiver; ///< 如果可以使用 archiver/unarchiver 歸/解檔則返回 YES BOOL _hasCustomClassFromDictionary; ///< 類/泛型自定義型別,例如需要在陣列中實現不同型別的轉換需要用到 /* 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,如果沒有對映到 keyPath 則返回 nil NSArray *_mappedToKeyArray; ///< key 或者 keyPath 的陣列,如果沒有對映多個鍵的話則返回 nil YYClassPropertyInfo *_info; ///< 屬性資訊,詳見上文 YYClassPropertyInfo && property_t 章節 _YYModelPropertyMeta *_next; ///< 如果有多個屬性對映到同一個 key 則指向下一個模型屬性元 } @end 複製程式碼
-
_YYModelMeta
物件的宣告定義:@interface _YYModelMeta : NSObject { @package YYClassInfo *_classInfo; /// Key:被對映的 key 與 keyPath,Value:_YYModelPropertyMeta. NSDictionary *_mapper; /// Array<_YYModelPropertyMeta>,當前模型的所有 _YYModelPropertyMeta 陣列 NSArray *_allPropertyMetas; /// Array<_YYModelPropertyMeta>,被對映到 keyPath 的 _YYModelPropertyMeta 陣列 NSArray *_keyPathPropertyMetas; /// Array<_YYModelPropertyMeta>,被對映到多個 key 的 _YYModelPropertyMeta 陣列 NSArray *_multiKeysPropertyMetas; /// 對映 key 與 keyPath 的數量,等同於 _mapper.count NSUInteger _keyMappedCount; /// 模型 class 型別 YYEncodingNSType _nsType; ///作用:判斷YYModel一系列協議方法是否實現 BOOL _hasCustomWillTransformFromDictionary;//解析前是否需要更改字典 BOOL _hasCustomTransformFromDictionary;//字典轉模型後是否需要補充處理 BOOL _hasCustomTransformToDictionary;//模型轉字典後是否需要補充處理 BOOL _hasCustomClassFromDictionary;//是否需要根據dic的內容轉換為不同型別的模型 } @end 複製程式碼
-
二、YYModel的具體使用 --- NSObject+YYModel 提供呼叫的介面
1、JSON資料轉換為model實體
- 1.json資料轉model資料,
+ (nullable instancetype)yy_modelWithJSON:(id)json;
,內部呼叫2; - 2.NSDictionary資料轉model資料,
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
,內部呼叫4;LGModel *model = [LGModel yy_modelWithDictionary:dict]; 複製程式碼
- 3.json資料為model物件賦值,
- (BOOL)yy_modelSetWithJSON:(id)json;
,內部呼叫4; - 4.NSDictionary資料為model物件賦值,
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
,json轉model的最終實現方法;LGModel *model1 = [[LGModel alloc]init]; [model1 yy_modelSetWithDictionary:dict]; 複製程式碼
2、model資料轉換為JSON資料
- 1.model轉為json資料,
- (nullable id)yy_modelToJSONObject;
,model資料轉換為JSON資料,最終實現 - 2.model轉為NSData,
- (nullable NSData *)yy_modelToJSONData;
,內部呼叫1 - 3.model轉為json字串,
- (nullable NSString *)yy_modelToJSONString;
,內部呼叫1
3、其他方法
- 物件深拷貝
- (nullable id)yy_modelCopy;
- 物件資料持久化儲存
- 存:
- (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder;
- 取:
- (id)yy_modelInitWithCoder:(NSCoder *)aDecoder;
- 存:
- 物件hash值
- (NSUInteger)yy_modelHash;
- 物件是否相等
- (BOOL)yy_modelIsEqual:(id)model;
- 物件描述
- (NSString *)yy_modelDescription;
4、集合相應方法
- json-array集體例項化
/** 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; 複製程式碼
- json-object集體例項化
/** 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","user2": {name:"Joe"}} @return A dictionary,or nil if an error occurs. */ + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; 複製程式碼
5、協議方法 --- 用來處理對映中的各種問題
5.1、Model 屬性名和 JSON 中的 Key 不相同 + (nullable NSDictionary<NSString *,id> *)modelCustomPropertyMapper;
,
//返回一個 Dict,將 Model 屬性名對對映到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"messageId":@[@"id",@"ID",@"book_id"]};
}
複製程式碼
- 你可以把一個或一組
json key (key path)
對映到一個或多個屬性。如果一個屬性沒有對映關係,那預設會使用相同屬性名作為對映。 - 在 json->model 的過程中:如果一個屬性對應了多個json key,那麼轉換過程會按順序查詢,並使用第一個不為空的值。
- 在 model->json 的過程中:如果一個屬性對應了多個 json key (key path),那麼轉換過程僅會處理第一個 json key (key path);如果多個屬性對應了同一個 json key,則轉換過過程會使用其中任意一個不為空的值。
5.2、自定義容器中的實體型別對映 + (nullable NSDictionary<NSString *,id> *)modelContainerPropertyGenericClass;
// 返回容器類中的所需要存放的資料型別 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass{
return @{@"books" : LGSubModel.class,@"infoDict" : [LGPerson class],@"likedUserIds" : @"NSNumber"
};
}
複製程式碼
- 在實際使用過過程中,
[LGPerson class]
,LGPerson.class
,@"LGPerson"
沒有明顯的區別。
5.3、根據dic來例項不同類的型別 + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
@implementation LGPerson
+(Class)modelCustomClassForDictionary:(NSDictionary *)dictionary {
if ([dictionary[@"gender"] integerValue] == 1) {
return LGMan.class;
}
return self;
}
@end
複製程式碼
5.4、黑名單(不處理的屬性)+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
5.5、白名單(只處理的屬性) + (nullable NSArray<NSString *> *)modelPropertyWhitelist;
// 如果實現了該方法,則處理過程中會忽略該列表內的所有屬性
+(NSArray<NSString *> *)modelPropertyBlacklist {
return @[@"subject"];
}
// 如果實現了該方法,則處理過程中不會處理該列表外的屬性
+ (NSArray<NSString *> *)modelPropertyWhitelist {
return @[@"name",@"age",@"num"];
}
複製程式碼
5.6、解析前更改字典資訊 - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
,發生在字典轉模型之前,最後對網路字典做一次處理;
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic{
if ([dic[@"gender"] integerValue] == 1) {
return nil;//不接受男性
}
return dic;
}
複製程式碼
5.7、資料校驗與自定義轉換,字典轉模型補充,- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
,YYModel無法處理或處理後格式型別等不正確,可以在這裡重新賦值處理;
// 當 JSON 轉為 Model 完成後,該方法會被呼叫。
// 你可以在這裡對資料進行校驗,如果校驗不通過,可以返回 NO,則該 Model 會被忽略。
// 你也可以在這裡做一些自動轉換不能完成的工作。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic{
NSNumber *interval = dic[@"timeInterval"];
if (![interval isKindOfClass:[NSNumber class]]) {
return NO;
}
_createTime = [NSDate dateWithTimeIntervalSince1970:[interval floatValue]];
return YES;
}
複製程式碼
5.8、資料校驗與自定義轉換,模型轉字典補充,- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
,同樣,轉為json時一樣有格式或型別不正確,可以在這裡重新賦值處理;
// 當 Model 轉為 JSON 完成後,該方法會被呼叫。
// 你可以在這裡對資料進行校驗,如果校驗不通過,可以返回 NO,則該 Model 會被忽略。
// 你也可以在這裡做一些自動轉換不能完成的工作。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
if (!_createTime) {
return NO;
}
dic[@"timeInterval"] = @([_createTime timeIntervalSince1970]) ;
return YES;
}
複製程式碼
三、YYModel實現json轉model的原始碼邏輯
YYModel呼叫邏輯流程圖:
1、YYModel使用yy_modelWithJSON
作為JSON模型轉換的入口,將傳入的物件轉換成字典,呼叫yy_modelWithDictionary:
,這個方法的內部實現如下:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
/// 獲取當前模型類
Class cls = [self class];
///2、通過class 獲取到各種資訊,然後封裝到_YYModelMeta中
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
///是否需要根據字典內容修改模型類
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
///模型類例項化
NSObject *one = [cls new];
///3、實現JSON轉模型功能
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
複製程式碼
2、metaWithClass:
通過class 獲取到各種資訊,然後封裝到_YYModelMeta中,返回快取的_YYModelMeta資訊,實現如下:
/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
if (!cls) return nil;
///宣告快取模型類和類資訊的字典,key為類名
static CFMutableDictionaryRef cache;
static dispatch_once_t onceToken;
///保證執行緒安全鎖的宣告
static dispatch_semaphore_t lock;
dispatch_once(&onceToken,^{///保證快取字典只例項化一次
///例項化快取字典
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(),&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);
///鎖建立
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER);///鎖啟用
///以類名為key從快取取類資訊_YYModelMeta
_YYModelMeta *meta = CFDictionaryGetValue(cache,(__bridge const void *)(cls));
dispatch_semaphore_signal(lock);///鎖關閉
if (!meta || meta->_classInfo.needUpdate) {
///2.1、快取未取到類資訊meta,根據model類cls去例項化該類資訊
meta = [[_YYModelMeta alloc] initWithClass:cls];
if (meta) {
dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER);///鎖啟用
///快取類資訊meta,類名為key
CFDictionarySetValue(cache,(__bridge const void *)(cls),(__bridge const void *)(meta));
dispatch_semaphore_signal(lock);///鎖關閉
}
}
return meta;
}
複製程式碼
2.1、快取未取到類資訊meta,根據model類cls去例項化該類資訊, initWithClass:
方法有點長,其實現如下:
- (instancetype)initWithClass:(Class)cls {
///2.1.1、從Class中獲取類資訊,並封裝成物件
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
if (!classInfo) return nil;
self = [super init];
// Get black list-獲取黑名單
NSSet *blacklist = nil;
if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
if (properties) {
blacklist = [NSSet setWithArray:properties];
}
}
// Get white list-獲取白名單
NSSet *whitelist = nil;
if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
if (properties) {
whitelist = [NSSet setWithArray:properties];
}
}
// Get container property’s generic class - 返回容器類中的所需要存放的資料型別
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) {/// key是字串,obj是容器中存放的例項的類物件
if (![key isKindOfClass:[NSString class]]) return;//key必須是字串型別
///獲取容器中存放物件的類(結果可能是元類或者NSString類物件)
Class meta = object_getClass(obj);
if (!meta) return;//防止異常,也就是說防止我們協議方法中返回的對映中Value是沒有類物件或者元類的其他內容,如nil
if (class_isMetaClass(meta)) {
//處理[LGPerson class],LGPerson.class 兩種型別
tmp[key] = obj;
} else if ([obj isKindOfClass:[NSString class]]) {
//處理@"LGPerson"型別
Class cls = NSClassFromString(obj);
if (cls) {
tmp[key] = cls;
}
}
}];
genericMapper = tmp;//容器類屬性以及對應Class 的字典
}
}
// Create all property metas.-獲取所有要解析的屬性,包括排除黑名單、驗證白名單、驗證是否有getter 和setter 等
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;//不在白名單內不處理
///2.1.2、例項化_YYModelPropertyMeta
_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;
// create mapper
NSMutableDictionary *mapper = [NSMutableDictionary new];
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
///屬性名和json中的鍵不一樣的,為屬性設定json中的key或者keyPath
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName,NSString *mappedToKey,BOOL *stop) {
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
//判斷屬性名對應的_YYModelPropertyMeta例項是否存在
if (!propertyMeta) return;
//移除屬性名對應的_YYModelPropertyMeta例項
[allPropertyMetas removeObjectForKey:propertyName];
//處理屬性名和json中的鍵一一對應的情況
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;//為json中的鍵賦值
///處理屬性名對應json中的多個鍵
} 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;//為json中的鍵賦值
}
}];
}
///將allPropertyMetas中剩下的值新增到mapper中
[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:)]);//根據dic的內容轉換為不同型別的模型方法是否實現
return self;
}
複製程式碼
- 2.1.1、從Class中獲取類資訊,並封裝成
YYClassInfo
物件YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
+ (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(),&kCFTypeDictionaryValueCallBacks);//類資訊快取d字典例項化 metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(),&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) { //2.1.1.1 更新類資訊 [info _update]; } dispatch_semaphore_signal(lock); if (!info) { //2.1.1.2快取中沒有獲取到類資訊,根據cls,初始化類資訊 info = [[YYClassInfo alloc] initWithClass:cls]; if (info) { dispatch_semaphore_wait(lock,DISPATCH_TIME_FOREVER); ///快取到字典 CFDictionarySetValue(info.isMeta ? metaCache : classCache,(__bridge const void *)(info)); dispatch_semaphore_signal(lock); } } return info; } 複製程式碼
- 2.1.1.1 更新類資訊
_update
方法- (void)_update { ///清空原有的成員列表,方法,屬性陣列 _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; //獲取方法列表 unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls,&methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i < methodCount; i++) { //對Method 做了一層封裝,封裝成了YYClassMethodInfo YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; if (info.name) methodInfos[info.name] = info; } free(methods); } // 3.獲取屬性列表 unsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList(cls,&propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; i++) { // 對Property做了一層封裝,封裝成了YYClassPropertyInfo YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; if (info.name) propertyInfos[info.name] = info; } free(properties); } // 4.獲取示例變數列表 unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls,&ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { // 對成員變數做了一層封裝,封裝成了YYClassIvarInfo YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); } if (!_ivarInfos) _ivarInfos = @{}; if (!_methodInfos) _methodInfos = @{}; if (!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO; } 複製程式碼
- 2.1.1.2 根據cls,初始化類資訊,YYClassInfo的
initWithClass:
方法;- (instancetype)initWithClass:(Class)cls { if (!cls) return nil; self = [super init]; _cls = cls; _superCls = class_getSuperclass(cls); _isMeta = class_isMetaClass(cls); if (!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls)); } _name = NSStringFromClass(cls); [self _update]; _superClassInfo = [self.class classInfoWithClass:_superCls]; return self; } 複製程式碼
- 2.1.1.1 更新類資訊
- 2.1.2、例項化
_YYModelPropertyMeta
的+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic
方法
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
// support pseudo generic class with protocol name
if (!generic && propertyInfo.protocols) {
for (NSString *protocol in propertyInfo.protocols) {
Class cls = objc_getClass(protocol.UTF8String);
if (cls) {
generic = cls;
break;
}
}
}
_YYModelPropertyMeta *meta = [self new];
meta->_name = propertyInfo.name;
meta->_type = propertyInfo.type;
meta->_info = propertyInfo;
meta->_genericCls = generic;
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
meta->_nsType = YYClassGetNSType(propertyInfo.cls);
} else {
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;
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;
}
}
meta->_cls = propertyInfo.cls;
if (generic) {
meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
} else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
}
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;
}
}
if (meta->_getter && meta->_setter) {
/*
KVC invalid type: KVC失效型別:長整型 雙精度 指標(SEL/CF物件)
long double
pointer (such as SEL/CoreFoundation object)
*/
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;
}
複製程式碼
3、真正來實現JSON模型轉換功能的是yy_modelSetWithDictionary:
,實現如下:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
// 獲取類資訊,這裡如果使用的是原來的類,則其實是從快取中取出來的,因為在前面已經呼叫過metaWithClass方法了。如果是設定了轉換的類,則可能會再重新完整執行一次metaWithClass。
_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;
}
// 2.自定義的一個context 結構體,把model 的資訊、model 物件指標、以及引數字典賦值上
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
if (modelMeta-> >= 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,CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),&context);
}
} else {//如果轉換屬性個數小於字典裡個鍵值對個數
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,modelMeta->_keyMappedCount),&context);
}
// 最後,如果有一些特殊的屬性,需要自己轉換賦值的話,再處理一下
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
複製程式碼
3.1、CFDictionaryApplyFunction
和CFArrayApplyFunction
總體概述
上面真正起到遍歷賦值的方法是CFDictionaryApplyFunction
和CFArrayApplyFunction
,通過這兩個CoreFoundation的方法來遍歷傳入的字典來給模型類賦值。這兩個方法和OC自帶的遍歷方法相比,會帶來不少效能上的提升,缺點就是寫起來相當麻煩。這兩個方法都有一個回撥函式(ModelSetWithDictionaryFunction
和ModelSetWithPropertyMetaArrayFunction
),在遍歷字典時,將字典的每一個value賦值給對應的模型類中的對應屬性。
-
CFDictionaryApplyFunction
和CFArrayApplyFunction
在官方檔案裡有解釋:
// Calls a function once for each key-value pair in a dictionary.
// 對於字典裡的每一個鍵值對,都會呼叫一次applier 方法
void CFDictionaryApplyFunction(CFDictionaryRef theDict,CFDictionaryApplierFunction applier,void *context);
複製程式碼
//Calls a function once for each element in range in an array。
// 對於陣列中指定range返回的每一個元素呼叫一次applier
void CFArrayApplyFunction(CFArrayRef theArray,CFRange range,CFArrayApplierFunction applier,void *context);
複製程式碼
YYModel中對兩個回撥函式的實現如下:
3.2、CFDictionaryApplyFunction
的回撥函式ModelSetWithDictionaryFunction
的實現:
static void ModelSetWithDictionaryFunction(const void *_key,const void *_value,void *_context) {
ModelSetContext *context = _context;
// 1.從上下文中取到model 的資訊
__unsafe_unretained _YYModelMeta meta = (__bridge _YYModelMeta )(context->modelMeta);
// 2.從轉換字典中取到屬性物件
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
__unsafe_unretained id model = (__bridge id)(context->model);
// 3.以防有多個相同key 的不同值
while (propertyMeta) {
if (propertyMeta->_setter) {
// 為model 的該屬性賦值。
ModelSetValueForProperty(model,(__bridge __unsafe_unretained id)_value,propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
複製程式碼
而ModelSetValueForProperty
方法中會根據屬性的型別呼叫objc_msgSend
來賦相應型別的值。例如字串型別的賦值:
if (meta->_nsType == YYEncodingTypeNSString) {
((void ()(id,SEL,id))(void ) objc_msgSend)((id)model,meta->_setter,value);
}
複製程式碼
3.3、CFArrayApplyFunction
的回撥函式ModelSetWithPropertyMetaArrayFunction
與字典的處理方式類似,只不過applier 中的引數直接就是屬性物件罷了。
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;
// 這裡因為value 的值,物件的可能有keyPath,也有直接的key。所以用不同的方式來取value
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) {
// 獲取model 的指標
__unsafe_unretained id model = (__bridge id)(context->model);
// 這裡就是為model 賦值啦
ModelSetValueForProperty(model,value,propertyMeta);
}
}
複製程式碼
由於本人水平有限,文中如有不足之處,望大神指出。
如果你看完後覺得對你有所幫助,勿忘點贊
+關注
。
附本文的Demo,贈人玫瑰,手有餘香。