MJExtension 基本上所有的iOS開發(fā)都用過(guò)椿肩,Json <---> Model。
主要的機(jī)制是采用了Runtime的反射機(jī)制馏慨,有興趣學(xué)習(xí)Runtime的同學(xué)可以看看音婶。
整理思路
如果你想要寫一個(gè) Json轉(zhuǎn)Model的第三方,該怎么入手。
已知:
- 包含key value 的 dictionary
- model類
那么也就是蛛蒙,我們需要找出model里面的所有的property糙箍,然后把dictionary里面的value賦值給對(duì)應(yīng)的property。
偽代碼
+ (id)JsonToModel:(NSDictionary *)dictionary {
TestModel *model = [[TestModel alloc] init];
for(propertyName in TestModel){
[model setValue:dictionary[propertyName] forKey: propertyName];
}
}
說(shuō)干就干
方法入口
調(diào)用:入口方法 mj_objectWithKeyValues最終會(huì)調(diào)用mj_setKeyValues
+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
return [self mj_objectWithKeyValues:keyValues context:nil];
}
+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
return [[[self alloc] init] mj_setKeyValues:keyValues];
}
- (instancetype)mj_setKeyValues:(id)keyValues
{
return [self mj_setKeyValues:keyValues context:nil];
}
核心方法
/**
核心代碼:
*/
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對(duì)象
keyValues = [keyValues mj_JSONObject];
Class clazz = [self class];
//通過(guò)封裝的方法回調(diào)一個(gè)通過(guò)運(yùn)行時(shí)編寫的牵祟,用于返回屬性列表的方法深夯。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 1.取出屬性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
...
...
安全判斷代碼
...
...
// 3.賦值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
}
}];
return self;
}
上面代碼做了一定程度的簡(jiǎn)化,當(dāng)然其實(shí)可以看的出來(lái)和前面的偽代碼有點(diǎn)類似了课舍。主要的邏輯就是:
- 獲取Dictionary
- 從類里面拿到所有的propertyName
- 找到dictionary里面的value賦值給model
反射獲取類中的屬性
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
{
// 獲得成員變量
NSArray *cachedProperties = [self properties];
// 遍歷成員變量
BOOL stop = NO;
for (MJProperty *property in cachedProperties) {
enumeration(property, &stop);
if (stop) break;
}
}
#pragma mark - 公共方法
+ (NSMutableArray *)properties
{
NSMutableArray *cachedProperties = [NSMutableArray array];
[self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1.獲得所有的成員變量
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(c, &outCount);
// 2.遍歷每一個(gè)成員變量
for (unsigned int i = 0; i<outCount; i++) {
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
// 過(guò)濾掉Foundation框架類里面的屬性
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
property.srcClass = c;
[property setOriginKey:[self propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
[cachedProperties addObject:property];
}
// 3.釋放內(nèi)存
free(properties);
}];
return cachedProperties;
}
通過(guò)*objc_property_t properties = class_copyPropertyList(c, &outCount)這個(gè)方法獲取所有的property塌西,然后mj_enumerateClasses是遍歷SuperClass,最后是把拿到的信息封裝成一個(gè)MJProperty筝尾。
PS
其中代碼進(jìn)行了相應(yīng)的簡(jiǎn)化捡需,其中有一些Property的緩存,類型的安全判斷都暫時(shí)忽略筹淫,只進(jìn)行思路的理解站辉。
紙上談來(lái)終覺(jué)淺
寫了一個(gè)Dome,大大簡(jiǎn)化了MJExtension损姜,目的是為了淺嘗Runtime的魅力饰剥。基本思路如最上面的偽代碼摧阅。其中很多安全判斷汰蓉,省略了些許。
這里有一個(gè)Property Attribute棒卷,蘋果文檔比較詳細(xì)參考:文檔
Demo 地址:我是Demo
//Demo Class
#import "NSObject+TestExtension.h"
#import <objc/runtime.h>
static NSSet *foundationClasses_;
@implementation NSObject (TestExtension)
+ (instancetype)test_JsonToModelWithDictionary:(NSDictionary *)dict
{
Class clazz = [self class];
id model = [[clazz alloc] init];
unsigned int count;
objc_property_t *propertys = class_copyPropertyList(clazz, &count);
//1. 遍歷Class中的Property
for (int i = 0; i < count; i++) {
NSString *propertyName = @(property_getName(propertys[i]));
NSString *propertyAttribute =@(property_getAttributes(propertys[i]));
// 2. 獲取Value
id value = [dict objectForKey:propertyName];
if (!value || [value isKindOfClass:[NSNull class]]) {
continue;
}
// 3. 如果是其他類顾孽,遞歸調(diào)用
Class propertyClazz = [self getClassFromAttrs:propertyAttribute];
if (propertyClazz) {
value = [propertyClazz test_JsonToModelWithDictionary:value];
}
[model setValue:value forKey:propertyName];
}
return model;
}
+ (Class)getClassFromAttrs:(NSString *)attrs
{
NSUInteger dotLoc = [attrs rangeOfString:@","].location;
NSString *code = nil;
NSUInteger loc = 1;
if (dotLoc == NSNotFound) { // 沒(méi)有,
code = [attrs substringFromIndex:loc];
} else {
code = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)];
}
if(code.length > 3 && [code hasPrefix:@"@\""])
{
// 去掉@"和",截取中間的類型名稱
code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
Class clazz = NSClassFromString(code);
if (![self isClassFromFoundation:clazz]) {
return clazz;
}
}
return nil;
}
+ (BOOL)isClassFromFoundation:(Class)c
{
if (foundationClasses_ == nil) {
foundationClasses_ = [NSSet setWithObjects:
[NSURL class],
[NSDate class],
[NSValue class],
[NSData class],
[NSError class],
[NSArray class],
[NSDictionary class],
[NSString class],
[NSAttributedString class], nil];
}
if (c == [NSObject class]) return YES;
__block BOOL result = NO;
[foundationClasses_ enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) {
if ([c isSubclassOfClass:foundationClass]) {
result = YES;
*stop = YES;
}
}];
return result;
}
@end