像使用MJExtension那样使用YYModel

这几天在公司没什么项目, 就赶紧利用难得的闲暇时间充电学习, 这里学习了一下YYModel, 并和之前项目中一直使用的MJExtension做一个对比, 下面是自己的一些见解

1 MJExtension在字典和模型的互转上较YYModel灵活些, 比如可以使用+ (id)mj_replacedKeyFromPropertyName121:(NSString*)propertyName方法统一对JSON中的keyproperty属性名做一个统一转换, 试想, 如果模型有很多属性, 而json返回的数据都是下划线那种格式而不是我们OC中常用的驼峰命名, 如果使用+ (NSDictionary*)modelCustomPropertyMapper返回字典一个一个手写就很蛋疼了, 这里我给MJExtension一个赞.

2 MJExtesionkeypropertyName的映射支持上更为广范, 比如可以在方法+ (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,
                @"" :  (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 )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 
@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= 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

你可能感兴趣的:(像使用MJExtension那样使用YYModel)