像使用MJExtension那样使用YYModel

作者: 六月天空 | 来源:发表于2017-04-14 19:37 被阅读615次

    这几天在公司没什么项目, 就赶紧利用难得的闲暇时间充电学习, 这里学习了一下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,
                    @"<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

    相关文章

      网友评论

      本文标题:像使用MJExtension那样使用YYModel

      本文链接:https://www.haomeiwen.com/subject/nmlgattx.html