MJExtension实现原理简单剖析
剖析MJExtension实现字典转模型的大致流程,我们不难发现分为4步:
1.遍历模型中的所有属性
2.解析属性的类型
3.以属性名为key在字典中寻找对应的值
4.根据属性的类型转化成对应的值并赋值
首先实现第一步:
遍历模型以及模型父类的所有属性,重新封装一个Property类,然后将Property对象放在数组中以供调用
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class Property;
//遍历属性的block
typedef void(^PropertyEnumeration)(Property *property, BOOL *stop);
@interface NSObject (Property)
+ (void)enumerateProperties:(PropertyEnumeration)enumeration;
+ (NSMutableArray *)properties;
@end
NS_ASSUME_NONNULL_END
@implementation NSObject (Property)
//枚举所有的属性
+ (void)enumerateProperties:(PropertyEnumeration)enumeration {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSArray *properties = [self properties];
dispatch_semaphore_signal(semaphore);
BOOL stop = NO;
for (Property *property in properties) {
enumeration(property, &stop);
if (stop) {
break;
}
}
}
+ (NSMutableArray *)properties {
__block NSMutableArray *properties = [NSMutableArray new];
//遍历类本身以及父类 基础类除外
[self enumSuperClasses:^(Class _Nonnull __unsafe_unretained c, BOOL * _Nonnull stop) {
unsigned int count = 0;
objc_property_t *obj_properties = class_copyPropertyList(c, &count);
for (int i = 0; i < count; i ++) {
Property *property = [[Property alloc]init];
property.property = obj_properties[i];
property.srcClass = c;
[properties addObject:property];
}
}];
return properties;
}
@end
封装一个Property类,表示我们解析出来的属性
NS_ASSUME_NONNULL_BEGIN
@interface Property : NSObject
@property (nonatomic, assign) objc_property_t property;
@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) PropertyType *type;
//属性所属的类
@property (nonatomic, assign) Class srcClass;
- (void)setValue:(id)value forObject:(id)object;
- (id)valueForObject:(id)object;
@end
NS_ASSUME_NONNULL_END
@implementation Property
- (void)setProperty:(objc_property_t)property {
_property = property;
//属性名
_name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
//成员类型
NSString *attributed = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
// NSLog(@"name = %@",_name);
// NSLog(@"attributed = %@", attributed);
// attributed = @"T@\"NSString\"";
NSString *code = nil;
NSArray *attributeArray = [attributed componentsSeparatedByString:@","];
if (attributeArray.count > 0) {
NSString *attributeStr = attributeArray[0];
code = [attributeStr substringWithRange:NSMakeRange(1, attributeStr.length - 1)];
}
// NSLog(@"code = %@",code);
PropertyType *type = [[PropertyType alloc]init];
_type = type;
type.code = code;
}
- (void)setValue:(id)value forObject:(id)object {
[object setValue:value forKey:self.name];
}
- (id)valueForObject:(id)object {
return [self valueForKey:self.name];
}
@end
2.解析属性的类型:
根据属性的property_getAttributes,获取到属性的属性串,解析属性串我们可以得到属性类型,这里我们封装了一个PropertyType来表示属性类型
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface PropertyType : NSObject
@property (nonatomic, copy) NSString *code;
@property (nonatomic, readonly) BOOL idType;
@property (nonatomic, readonly) BOOL numberType;
@property (nonatomic, readonly) BOOL boolType;
@property (nonatomic, readonly) Class typeClass;
@property (nonatomic, readonly) BOOL fromFoundation;
@property (nonatomic, readonly) BOOL KVCDisable;
@end
NS_ASSUME_NONNULL_END
@implementation PropertyType
- (void)setCode:(NSString *)code {
_code = code;
if ([code isEqualToString:PropertyTypeId]) {
//ID类型
_idType = YES;
} else if (code.length == 0) {
//无类型
_KVCDisable = YES;
} else if (code.length > 3 && [code hasPrefix:@"@\""]) {
//正常类类型
_code = [code substringWithRange:NSMakeRange(2, code.length - 3)];
_typeClass = NSClassFromString(_code);
_fromFoundation = [Foundation isClassFromFoundation:_typeClass];
_numberType = [_typeClass isSubclassOfClass:[NSNumber class]];
} else if ([code isEqualToString:PropertyTypeSEL] ||
[code isEqualToString:PropertyTypeIvar] ||
[code isEqualToString:PropertyTypeMethod]) {
_KVCDisable = YES;
}
//是否为数字类型
NSString *lowerCode = _code.lowercaseString;
NSArray *numberTypes = @[PropertyTypeInt,PropertyTypeShort,PropertyTypeBOOL1,PropertyTypeBOOL2,PropertyTypeFloat,PropertyTypeDouble,PropertyTypeLong,PropertyTypeLongLong,PropertyTypeChar];
if ([numberTypes containsObject:lowerCode]) {
_numberType = YES;
if ([lowerCode isEqualToString:PropertyTypeBOOL1] ||
[lowerCode isEqualToString:PropertyTypeBOOL2]) {
_boolType = YES;
}
}
}
@end
到此为止,我们已经重组了所有的模型属性,接下来就是赋值环节了,我们定义一个字典,一个数据模型,试着转一下:
数据模型
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSInteger age;
@property BOOL isMan;
@end
NS_ASSUME_NONNULL_END
- (NSString *)description
{
return [NSString stringWithFormat:@"{\n class = %@\n name = %@\n age = %ld \n isMan = %d \n}",
NSStringFromClass(self.class),
_name,
_age,
_isMan];
}
字典
NSDictionary *dict = @{@"name":@"xiaoxiao",
@"age":@13,
@"isMan":@0
};
Person *p = [Person objectWithKeyValues:dict];
NSLog(@"person = %@",p);
转换赋值函数
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (KeyValue)
+ (instancetype)objectWithKeyValues:(id)keyValues;
@end
NS_ASSUME_NONNULL_END
@implementation NSObject (KeyValue)
+ (instancetype)objectWithKeyValues:(id)keyValues {
if (!keyValues) {
return nil;
}
return [[[self alloc] init]setKeyValues:keyValues];
}
- (instancetype)setKeyValues:(id)keyValues {
NSArray *propertyArray = [self.class properties];
for (Property *property in propertyArray) {
PropertyType *type = property.type;
Class typeClass = type.typeClass;
id value = [keyValues valueForKey:property.name];
if (!value) {
continue;
}
if (type.boolType) {
NSLog(@"BOOL");
} else if(type.idType) {
NSLog(@"ID");
} else if (type.numberType) {
NSLog(@"Number");
if ([value isKindOfClass:[NSString class]]) {
value = [[[NSNumberFormatter alloc]init]numberFromString:value];
}
} else {
NSLog(@"%@",NSStringFromClass(typeClass));
}
[self setValue:value forKey:property.name];
}
return self;
}
@end
运行打印结果:
2019-04-17 12:17:37.007307+0800 RunTimeLearnApp[7833:433702] NSString
2019-04-17 12:17:37.007425+0800 RunTimeLearnApp[7833:433702] Number
2019-04-17 12:17:37.007519+0800 RunTimeLearnApp[7833:433702] BOOL
2019-04-17 12:17:37.007666+0800 RunTimeLearnApp[7833:433702] person = {
class = Person
name = xiaoxiao
age = 13
isMan = 0
}
这只是MJExtension实现的最基础的原理,作为商业性项目的MJExtension做的更完善,包括键值替换、多级映射等,当然也更加全面稳定。
这只是一个学习心得,功力不够,如有错误欢迎@,共同学习进步。
附上MJExtension三方库链接:
MJExtension源代码