像使用MJExtension那样使用YYModel
这几天在公司没什么项目, 就赶紧利用难得的闲暇时间充电学习, 这里学习了一下YYModel, 并和之前项目中一直使用的MJExtension做一个对比, 下面是自己的一些见解
1 MJExtension
在字典和模型的互转上较YYModel
灵活些, 比如可以使用+ (id)mj_replacedKeyFromPropertyName121:(NSString*)propertyName
方法统一对JSON
中的key
和property
属性名做一个统一转换, 试想, 如果模型有很多属性, 而json
返回的数据都是下划线那种格式而不是我们OC中常用的驼峰命名, 如果使用+ (NSDictionary*)modelCustomPropertyMapper
返回字典一个一个手写就很蛋疼了, 这里我给MJExtension一个赞.
2 MJExtesion
在key
和propertyName
的映射支持上更为广范, 比如可以在方法+ (NSDictionary*)mj_replacedKeyFromPropertyName
中返回一个这样的的字典
@{@"tag" : @"topics[0].status.tag"}
, 也就是可以和一个数组指定下标元素做映射, 而YYModel就做不到
这两点我个人觉得在开发中还是很实用的功能,
3 个人觉得MJExtension
方法很全, 但是也难免会稍显有点笨重, 不如YYModel
显得那么精炼专门用于字典和模型的转换, 并且转换效率较高, 其中和作者缜密的思路是分不开的, 从作者运用CFMutableDictionaryRef
而不是使用NSMutableDictionary
可见一斑, 更加底层, 还有__unsafe_unretained
的使用, 避开内存管理的开销,
如果能像使用MJExtension
那样使用YYModel
岂不妙哉! 在读懂YYModel
源码的基础上我主要添加了上述那个两个功能, 不喜勿喷昂, 下面主要说明我修改的地方
1 方法YYNSNumberCreateFromID
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) {
static NSCharacterSet *dot;
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
dic = @{@"true" : @(YES),
@"false" : @(NO),
@"yes" : @(YES),
@"no" : @(NO),
@"y" : @(YES),
@"n" : @(NO),
@"nil" : (id)kCFNull,
@"null" : (id)kCFNull,
@"(null)" : (id)kCFNull,
@"<null>" : (id)kCFNull};
});
if (!value || value == (id)kCFNull) return nil;
if ([value isKindOfClass:[NSNumber class]]) return value;
if ([value isKindOfClass:[NSString class]]) {
NSNumber *num = dic[((NSString *)value).lowercaseString];
if (num) {
if (num == (id)kCFNull) return nil;
return num;
}
if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
double num = atof(cstring);
if (isnan(num) || isinf(num)) return nil;
return @(num);
} else {
const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
return @(atoll(cstring));
}
}
return nil;
}
之前看好大一串各种大小写, 其实都是同一个字符串, 把key转成小写再比较不就好了嘛! 嘿嘿,
2 YYMode协议
+ (NSString *)replaceKeyFromPropertyName:(NSString *)propertyName;
YYModel协议中增加一个这个方法, 没什么好说的.
3 _YYModelMeta的- (instancetype)initWithClass:(Class)cls
方法
这里我在if([clsrespondsToSelector:@selector(modelCustomPropertyMapper)])
这个if语句后增加了这些代码 至于为什么是在这之后而不是之前稍后再说
if ([cls respondsToSelector:@selector(replaceKeyFromPropertyName:)]) {
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
NSString *mappedToKey = [(id <YYModel>)cls replaceKeyFromPropertyName:name];
if (![mappedToKey isKindOfClass:[NSString class]]) return;
if (mappedToKey.length == 0) return;
if (!propertyMeta) return;// return 相当于忽略此次循环 继续下一次循环
[allPropertyMetas removeObjectForKey:name];
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;
}];
}
其实这个看源码应该很好理解, 之所以在那个if语句后就是说明方法1 +(NSDictionary *)modelCustomPropertyMapper
比方法2+ (NSString*)replaceKeyFromPropertyName:(NSString*)propertyName
的权限大, 当同时实现这两个方法, 方法1中的映射过的属性将不会再传入方法2的propertyName
参数了,
4 id YYValueForKeyPath(NSDictionary*dic, NSArray*keyPaths)
方法
这里为了支持映射到数组的功能,实现如下
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
id value = dic;
for (NSString *keyPath in keyPaths) {
NSUInteger left = [keyPath rangeOfString:@"["].location;
if (left != NSNotFound) {
NSString *sub = [keyPath substringToIndex:left];
if (![value isKindOfClass:[NSDictionary class]]) return nil;
value = value[sub];
if (![value isKindOfClass:[NSArray class]]) return nil;
NSUInteger right = [keyPath rangeOfString:@"]"].location;
if (right == NSNotFound) return nil;
NSString *idxStr = [keyPath substringWithRange:(NSRange){left + 1, right - left - 1}];
if (!idxStr.length) return nil;
NSInteger idx = idxStr.integerValue;
value = (NSArray *)value[idx];
} else {
if (value && ![value isKindOfClass:[NSDictionary class]]) return nil;
value = value ? value[keyPath] : dic[keyPath];
}
}
return value;
}
到这里就可以实现映射到数组了, 但是打印对象的modelToJSONString
, 和MJExtension
还是有些偏差, 问题出在哪了, 于是又对以下方法做一修改, 这个真是忙了我好久
5 方法 static id ModelToJSONObjectRecursive(NSObject*model)
这里主要修改了if(propertyMeta->_mappedToKeyPath)
这个if语句, 由于此方法比较长 我主要贴出我修改的部分, 他的都没动
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
NSMutableArray *superArr = nil;
NSMutableArray *subArr = nil;
BOOL contains = NO;
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
NSRange left = [key rangeOfString:@"["];
if (left.location != NSNotFound) {
NSString *sub = [key substringToIndex:left.location];
subDic = superDic[sub];
if (subDic) {
} else {
subDic = [NSMutableDictionary dictionary];
subArr = [NSMutableArray new];
superDic[sub] = subArr;
if (superArr && contains) [superArr addObject:superDic];
}
NSInteger start = left.location + left.length;
NSRange right = [key rangeOfString:@"]"];
if (right.location == NSNotFound) {
NSLog(@"the mapper of keypath %@ which class is %@ error", key, propertyMeta->_cls);
} else {
contains = YES;
NSString *countStr = [key substringWithRange:(NSRange){start, right.location - start}];
if (countStr.length == 0) {
NSLog(@"the mapper of keypath %@ which class is %@ error", key, propertyMeta->_cls);
} else {
NSInteger count = countStr.integerValue;
for (NSInteger i = 0; i < count; i++)
[subArr addObject:[NSNull null]];
}
}
superDic = subDic;
superArr = subArr;
subDic = nil;
subArr = nil;
if (i + 1 == max) if (superArr) [superArr addObject:value];
} else {
subDic = superDic[key];
if (subDic) {
} else {
if (i + 1 == max) {
superDic[key] = value;
if (superArr && contains) [superArr addObject:superDic];
} else {
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
if (superArr && contains) [superArr addObject:superDic];
}
}
contains = NO;
superDic = subDic;
subDic = nil;
}
}
} else {
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
}];
这里再贴下我测试用的代码
#import "YYModel.h"
@interface HHTag : NSObject <NSCoding>
@property (nonatomic, strong) NSString *tagName; ///< 标签名字,例如"上海·上海文庙"
@property (nonatomic, strong) NSString *tagScheme; ///< 链接 sinaweibo://...
@property (nonatomic, assign) int32_t tagType; ///< 1 地点 2其他
@property (nonatomic, assign) int32_t tagHidden;
@property (nonatomic, strong) NSURL *urlTypePic; ///< 需要加 _default
//@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *wbName;
@end
@implementation HHTag
YYCodingImplementation
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"wbName" : @"wb_name.newName.info[1].nameChangedTime[1].bbb.text[2].text.page[1].test[1]"};
// return @{@"wbName" : @"wb_name.newName.info[1].nameChangedTime[1].bbb.text[2].text.page.test"};
// return @{@"wbName": @"wb_name.info[1].nameChangedTime[1]"};
}
+ (NSString *)replaceKeyFromPropertyName:(NSString *)propertyName {
return [propertyName mapperWithType:NSStringMapperUnderLineFromCamel];
}
@end
HHTag *tag = [HHTag modelWithJSON:@{@"tag_hidden" : @2 , @"tag_name" : @"上海·上海文庙", @"tag_scheme" : @"http://www.scheme", @"tag_type" : @1, @"url_type_pic" : @"http://www.pic", @"tag_topic" : @"#today is hot", @"wb_name" : @{@"newName" : @{ @"info" : @[@"test-data", @{@"nameChangedTime" : @[@{@"aaa" : @"2013-01"}, @{@"bbb" : @{@"text" : @[@"2014-01", @"2014-02", @{@"text" : @{@"page" : @[@"2017-08", @{@"test" : @[@"2017-09", @"2017-10"]}]}}]}}]}] } }}];
NSLog(@"%@", [tag modelToJSONString]);
2017-04-14 19:26:48.002 YYKitDemo[6759:730764] {"tag_scheme":"http:\/\/www.scheme","url_type_pic":"http:\/\/www.pic","tag_type":1,"wb_name":{"newName":{"info":[null,{"nameChangedTime":[null,{"bbb":{"text":[null,null,{"text":{"page":[null,{"test":[null,"2017-10"]}]}}]}}]}]}},"tag_hidden":2,"tag_name":"上海·上海文庙"}
在做一下补充
#define YYCodingImplementation \
- (id)initWithCoder:(NSCoder *)decoder \
{ \
self = [super init]; \
return [self modelInitWithCoder:decoder]; \
} \
\
- (void)encodeWithCoder:(NSCoder *)encoder \
{ \
[self modelEncodeWithCoder:encoder]; \
}
#define YYCopyImplementation \
- (id)copyWithZone:(NSZone *)zone { return [self modelCopy]; }
#define YYHashImplementation \
- (NSUInteger)hash { return [self modelHash]; }
#define YYEqualImplementation \
- (BOOL)isEqual:(id)object { return [self modelIsEqual:object]; }
typedef NS_ENUM(NSUInteger, NSStringMapperType) {
NSStringMapperDefault = 0,// 不做转换
NSStringMapperFirstCharLower = 1,// 首字母变小写
NSStringMapperFirstCharUpper, // 首字母变大写
NSStringMapperUnderLineFromCamel, // 驼峰转下划线(loveYou -> love_you)
NSStringMapperCamelFromUnderLine, // 下划线转驼峰(love_you -> loveYou)
};
@interface NSString (YYMapper)
- (NSString *)mapperWithType:(NSStringMapperType)type;
@end
@interface NSString (__YYAdd)
/**
* 驼峰转下划线(loveYou -> love_you)
*/
- (NSString *)yy_underlineFromCamel;
/**
* 下划线转驼峰(love_you -> loveYou)
*/
- (NSString *)yy_camelFromUnderline;
/**
* 首字母变大写
*/
- (NSString *)yy_firstCharUpper;
/**
* 首字母变小写
*/
- (NSString *)yy_firstCharLower;
@end
@implementation NSString (__YYAdd)
- (NSString *)yy_underlineFromCamel {
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
for (NSUInteger i = 0; i<self.length; i++) {
unichar c = [self characterAtIndex:i];
NSString *cString = [NSString stringWithFormat:@"%c", c];
NSString *cStringLower = [cString lowercaseString];
if ([cString isEqualToString:cStringLower]) {
[string appendString:cStringLower];
} else {
[string appendString:@"_"];
[string appendString:cStringLower];
}
}
return string;
}
- (NSString *)yy_camelFromUnderline {
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
NSArray *cmps = [self componentsSeparatedByString:@"_"];
for (NSUInteger i = 0; i<cmps.count; i++) {
NSString *cmp = cmps[i];
if (i && cmp.length) {
[string appendString:[NSString stringWithFormat:@"%c", [cmp characterAtIndex:0]].uppercaseString];
if (cmp.length >= 2) [string appendString:[cmp substringFromIndex:1]];
} else {
[string appendString:cmp];
}
}
return string;
}
- (NSString *)yy_firstCharLower {
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
[string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].lowercaseString];
if (self.length >= 2) [string appendString:[self substringFromIndex:1]];
return string;
}
- (NSString *)yy_firstCharUpper {
if (self.length == 0) return self;
NSMutableString *string = [NSMutableString string];
[string appendString:[NSString stringWithFormat:@"%c", [self characterAtIndex:0]].uppercaseString];
if (self.length >= 2) [string appendString:[self substringFromIndex:1]];
return string;
}
@end
@implementation NSString (YYMapper)
- (NSString *)mapperWithType:(NSStringMapperType)type {
switch (type) {
case NSStringMapperDefault:
return self;
case NSStringMapperFirstCharLower:
return self.yy_firstCharLower;
case NSStringMapperFirstCharUpper:
return self.yy_firstCharUpper;
case NSStringMapperUnderLineFromCamel:
return self.yy_underlineFromCamel;
case NSStringMapperCamelFromUnderLine:
return self.yy_camelFromUnderline;
default:
break;
}
return self;
}
@end
第一次在简书发表文章,谢谢大家, 也很感谢YYModel的作者,一起进步加油, 有问题欢迎指出
gitHub地址:https://github.com/theSkyOfJune/YYModelExtension#demo-project