您的位置:时时app平台注册网站 > 编程知识 > iOS源码分析—YYModel(NSObject YYModel卡塔尔【时时

iOS源码分析—YYModel(NSObject YYModel卡塔尔【时时

2019-11-03 08:44

 

字符串和数组的相互转化

    /****************************字符串转成数组*************************************/
    NSString *Str = [NSString stringWithFormat:@"My,Hobby,Is,Running"];
    // 字符串转数组(以逗号分隔)
    NSArray *StrToArray = [Str componentsSeparatedByString:@","];
    NSLog(@"字符串转成数组: %@",StrToArray);

    /*****************************数组转成字符串案例1************************************/
    NSMutableArray *arrayM = [NSMutableArray arrayWithObjects:@"Coder",@"Zb",nil];
    // 移除@"Coder",这样可变数组中只有@[@"Zb",nil];
    [arrayM removeObject:[NSString stringWithFormat:@"Coder"]];
    // 数组转成字符串(以逗号分隔).arrayM只有一个@"Zb"对象,正是因为nil的出现,才能以逗号分隔,可见nil的重要性
    NSString *ArrayToStr = [arrayM componentsJoinedByString:@","];
    NSLog(@"数组转成字符串案例1: %@",ArrayToStr);

    /*****************************数组转成字符串案例2************************************/
    NSMutableArray *ArrM = [NSMutableArray arrayWithObjects:@"Coder",@"Zb",@"Add",nil];
    // 数组转成字符串(以逗号分隔)
    NSString *ArrMyToStr = [ArrM componentsJoinedByString:@","];
    // 注意:打印的字符串之间是有逗号的。这就是案例2的目的。
    NSLog(@"数组转成字符串案例2: %@",ArrMyToStr);

时时app平台注册网站 1

字符串和数组的相互转化.png


类型转化和兼容

YYModel的总体思想是以Model属性的类型为准,如果JSON中对应名称的value的类型和Model属性类型不一致,会对value的类型进行转化,保证和Model属性的类型一致。如果兼容不了,不进行属性赋值。下面分析一下ModelSetValueForProperty方法:

该方法上文中提到该方法是用来JSON转成Model的过程中对Model中的属性进行赋值的方法,该方法做了部分基本类型的兼容:

  • 如果Model的属性是数值类型,与之对应的JSON的value是其它类型,会进行转化,如下:

    if (meta->_isCNumber) {
      NSNumber *num = YYNSNumberCreateFromID(value);
      ModelSetNumberToProperty(model, num, meta);
      if (num) [num class]; // hold the number
    }
    

    YYNSNumberCreateFromID会将value转化成NSNumber类型,有可能返回nil。

  • 如果Model的属性是字符串类型,会判断value的类型,进一步处理:

    case YYEncodingTypeNSString:
    case YYEncodingTypeNSMutableString: {
      if ([value isKindOfClass:[NSString class]]) {
          ...
      } else if ([value isKindOfClass:[NSNumber class]]) {
          ... //转化成NSString
        } else if ([value isKindOfClass:[NSData class]]) {
          ... //转化成NSString
      } else if ([value isKindOfClass:[NSURL class]]) {
          ... //转化成NSString
        } else if ([value isKindOfClass:[NSAttributedString class]]) {
          ... //转化成NSString
        }
    break;
    

    根据value类型的不同,调用不得方法转成字符串类型。

  • 如果Model的属性是NSNumber类型,需要将value转化成NSNumber类型。

  • 如果Model的属性是NSData、NSDate、NSURL类型等,都需要value先转成这些类型。

  • 不是所有的类型都可以转化,如果属性的对象是数组、集合或者字典类型,但是value不是对应的类型,不会转化,直接返回,不进行后续的属性赋值。例如字典类型,代码如下:

    case YYEncodingTypeNSDictionary:
    case YYEncodingTypeNSMutableDictionary: {
      if ([value isKindOfClass:[NSDictionary class]]) { //value为字典类型是,进行处理,否则不处理
          ... 
        }
    } break;
    

字典

数组NSArray


1.传送一个字符串

NSArray *arr1=[NSArray arrayWithObject:@"zb"];

NSLog(@"%@",arr1);

输出结果

20160420 17:19:02.897 NSArray基本概念[22397:1156539] (
zb
)

2.传送多个字符串 数组中的nil是结束符.所以数组中的zb5,zb6不打印

NSArray *arr2=[NSArray arrayWithObjects:@"zb",@"zb2",@"zb3",@"zb4",nil,@"zb5",@"zb6",nil];

NSLog(@"%@",arr2);

输出结果

20160420 17:19:02.898 NSArray基本概念[22397:1156539] (

zb,

zb2,

zb3,

zb4

)

3.OC中的数组NSArray可以存储不同类型的数据

Person *p=[[Person alloc]init];

NSObject *obj=[[NSObject alloc]init];

NSArray *arr3=[NSArray arrayWithObjects:p,obj,@"zb",nil];

NSLog(@"arr3=%@",arr3);

输出结果

2016-04-20 17:19:02.899 NSArray基本概念[22397:1156539] arr3=(
    "<Person: 0x1004000e0>",
    "<NSObject: 0x100400130>",
    zb
)

5.获取数组中元素的个数

NSLog(@"count=%lu",[arr3 count]);

输出结果

20160420 17:19:02.899 NSArray基本概念[22397:1156539] count=3

6.获取数组中第零个元素

NSLog(@"数组中的第零个元素是%@",[arr3 firstObject]);

输出结果

2016-04-20 17:19:02.899 NSArray基本概念[22397:1156539] 数组中的第零个元素是<Person: 0x1004000e0>

7.获取数组中第一个元素

NSLog(@"数组中第一个元素是%@",[arr3 objectAtIndex:1]);

输出结果

2016-04-20 17:19:02.899 NSArray基本概念[22397:1156539] 数组中第一个元素是<NSObject: 0x100400130>

8.获取数组中最后一个元素

NSLog(@"数组中最后一个元素是%@",[arr3 lastObject]);

输出结果

2016-04-20 17:19:02.899 NSArray基本概念[22397:1156539] 数组中最后一个元素是zb

9.OC中的数组包含字符串的方法

NSArray *arr4=[NSArray arrayWithObjects:@"zhangbin",@"zb",@"ls",@"mz",nil];

if ([arr4 containsObject:@"zb"]) {

NSLog(@"arr4中包含zb");

}else{

NSLog(@"arr4中不包含zb");

}

输出结果

2016-04-20 17:19:02.899 NSArray基本概念[22397:1156539] arr4中包含zb

10.获取数组元素的常规写法与简写

10.1常规写法:

NSArray *arr6=@[@"zhangbin",@"zb",@"ls",@"mz"];
NSLog(@"%@",[arr6 objectAtIndex:0]);// 方法1

输出结果

20160420 17:19:02.899 NSArray基本概念[22397:1156539] zhangbin

10.2简写:

NSArray *arr6=@[@"zhangbin",@"zb",@"ls",@"mz"];
NSLog(@"%@",arr6[0]);// 方法2

输出结果

20160420 17:19:02.899 NSArray基本概念[22397:1156539] zhangbin

NSObject YYModel.m

首先定义了两个类,_YYModelMeta和_YYModelPropertyMeta,分别封装了Model的信息和Model中各属性的信息。

_YYModelMeta维护了Class的相关信息,下面是注释:

@interface _YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo; //关联的YYClassInfo对象
    NSDictionary *_mapper; //维护一个键值对,key是属性名,value是_YYModelPropertyMeta对象
    //维护一个数组,里面的元素是_YYModelPropertyMeta对象
    NSArray *_allPropertyMetas;
    //维护一个数组,里面的元素是_YYModelPropertyMeta对象
    NSArray *_keyPathPropertyMetas;
    //维护一个数组,里面的元素是_YYModelPropertyMeta对象
    NSArray *_multiKeysPropertyMetas;
    //通过映射相关
    NSUInteger _keyMappedCount;
    //对象类型
    YYEncodingNSType _nsType;
    ...
}
@end

_YYModelMeta是通过YYClassInfo对象的信息构建得到的。首先调用metaWithClass:cls方法,该方法如下:

  (instancetype)metaWithClass:(Class)cls {
    ...
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); //从缓存中取
    ...
    if (!meta || meta->_classInfo.needUpdate) {
        meta = [[_YYModelMeta alloc] initWithClass:cls]; //创建一个新的_YYModelMeta对象
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    return meta;
}

维护了一个键值对cache作为缓存,用cls作为key,调用CFDictionaryGetValue方法去缓存中查找,如果有,直接返回构建好的_____YYModelMeta对象,如果没有找到或者needUpdate属性标记为true,则根据cls创建一个新的_YYModelMeta对象,并且存入缓存。这样不需要每次都创建,提高了性能。

接下来看一下initWithClass:方法,

  1. 创建一个YYClassInfo对象,在上一篇文章中分析了YYClassInfo的创建方法;

  2. 判断调用层是否实现modelPropertyBlacklist方法,如果实现了,返回一组黑名单,数组中包含的属性名不会被转化。

  3. 判断调用层是否实现modelPropertyWhitelist方法,如果实现了,返回一组白名单,不在数组中的属性名不会被转化。

  4. 如果调用层实现了modelContainerPropertyGenericClass方法,则维护一个字典genericMapper,字典的value如果是NSString类型,需要转成Class类型。

  5. 遍历对象的属性,生成_YYModelPropertyMeta,代码注释如下:

    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) {
     for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) 
        {
             if (!propertyInfo.name) continue;
             //黑名单过滤
             if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
             //白名单过滤
            if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
             //根据classInfo和propertyInfo创建_YYModelPropertyMeta
            _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo                                                                  propertyInfo:propertyInfo                                                                       generic:genericMapper[propertyInfo.name]];
             if (!meta || !meta->_name) continue;
            if (!meta->_getter || !meta->_setter) continue;
            if (allPropertyMetas[meta->_name]) continue;
            allPropertyMetas[meta->_name] = meta; //存入allPropertyMetas字典中
        }
        curClassInfo = curClassInfo.superClassInfo;
    }
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; //赋值给_allPropertyMetas属性
    

    根据classInfo信息和propertyInfo信息创建_YYModelPropertyMeta对象,并用_allPropertyMetas字典存储,字典中的key是属性名,value是_YYModelPropertyMeta对象。

  6. 如果调用层实现了modelCustomPropertyMapper方法,说明存在JSON字典key和属性名之间的映射关系,需要遍历这些属性,做特殊处理,如下:

    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
         NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
     [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
             //这些属性名对应的_YYModelPropertyMeta从allPropertyMetas字典中删除
             _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) return;
            [allPropertyMetas removeObjectForKey:propertyName];
    
             //mappedToKey作为新的key,存入mapper,value是propertyMeta,同时对mappedToKey做一些分割处理,表示映射的路径
            if ([mappedToKey isKindOfClass:[NSString class]]) {
             ...
                if (keyPath.count > 1) {
                        propertyMeta->_mappedToKeyPath = keyPath;
                        [keyPathPropertyMetas addObject:propertyMeta];
                }
                 propertyMeta->_next = mapper[mappedToKey] ?: nil;
                 mapper[mappedToKey] = propertyMeta;  
             } 
             ...
         }];
    }
    
  7. 维护一个_mapper字典,key是mappedToKey,对于customMapper字典中不存在的属性,mappedToKey是其本身的属性名,value是创建的propertyMeta。同时维护一个_keyPathPropertyMetas数组和_multiKeysPropertyMetas数组,专门存储customMapper中存在的,即存在映射关系的_YYModelPropertyMeta。

    其中YYModelPropertyMeta类维护了property的相关信息,下面是相关注释:

    @interface _YYModelPropertyMeta : NSObject {
        @package
        NSString *_name;             //属性名
        YYEncodingType _type;        //属性类型
        YYEncodingNSType _nsType;    //属性如果是NSObject类型的对象,相应的类型
        BOOL _isCNumber;             //是否是数字类型
        Class _cls;                  //属性的类
        Class _genericCls;           //
        SEL _getter;                 //getter方法
        SEL _setter;                 //setter方法
        BOOL _isKVCCompatible;       //是否可以处理KVC
        BOOL _isStructAvailableForKeyedArchiver; //是否可以archiver/unarchiver
        BOOL _hasCustomClassFromDictionary; //
    
        NSString *_mappedToKey;      //该属性名对应的JSON字典中的key
        NSArray *_mappedToKeyPath;   //该属性名对应的JSON字典中的keyPath,keyPath是一个路径,例如AGE.NUM,该数组存储@[AGE,NUM]
        NSArray *_mappedToKeyArray;  //该属性名对应的JSON字典中的key,如果@[@"age",@"num"]
        YYClassPropertyInfo *_info;  //关联的YYClassPropertyInfo
        _YYModelPropertyMeta *_next; //另一个YYModelPropertyMeta对象,key名称相同
    }
    @end
    

    _YYModelPropertyMeta对象是通过YYClassPropertyInfo对象的信息构建得到的。

创建数组

1 //Int类型数组
2 var arr0 = Array<Int>()
3 
4 //或者
5 
6 var arr1 = [Int]()

 

字典的遍历(三种方法)


1.普通遍历NSDictionary

NSDictionary *dict5 = @{@"name":@"zb",@"age":@"18",@"height":@"170"};

for (int i=0;  i<dict5.count;   i) {
//取出字典中所有的key放到数组中keys中

NSArray *keys=[dict5 allKeys];

//根据数组中的下标i,取出数组中相应位置的key

NSString *key=keys[i];

//根据字典中的key取出唯一与之对应的value

NSString *value=dict5[key];

//打印数组中的每个key,以及每个key对应的value

NSLog(@"key = %@,value = %@",key,value);

}

2.快速遍历NSDictionary

NSDictionary *dict = @{@"name":@"zb",@"age":@"18",@"height":@"170"};

for (NSString *key in dict) {

NSLog(@"key = %@ ,value = %@",key,dict[key]);

}

3.利用block遍历NSDictionary(迭代器的方法)

NSDictionary *dict = @{@"name":@"zb",@"age":@"18",@"height":@"170"};

[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj,BOOL *stop){//obj换成value,输出也必须换成value

NSLog(@" key = %@, value = %@",key,obj);

}];

概述

​ iOS源码解析—YYModel(YYClassInfo)分析了如何根据OC的Class对象构建YYClassInfo对象,为接下来的JSON数据和Model转换作准备,这篇文章开始讲解NSObject YYModel。

数组

定义数组和定义字典的区别

1.定义字典

 NSDictionary *dict3=@{@"key":@"value",@"key":@"value"};

2.定义数组的形式一(注:指针对象必须得创建出来)

NSArray *array3=@[指针对象,NSNumber,@整形,@"字符串",nil];

3.定义数组的形式二

NSArray *array3=[NSArray arrayWithObjects:指针对象,NSNumber,@整形,@"字符串",nil];的

4.总结:从123可以得治字典中的对象都是字符串类型;数组中可以存放任意类型,只要OC支持的类型


结尾

YYModel作为一个负责JSON数据和Model转化的库,十分易用和高效,特别是做了一些类型的兼容和转化,避免了服务端接口数据类型和客户端Model对象类型不兼容导致的问题,例如执行了不存在的方法而导致崩溃。另一方面,对YYModel的学习在一定程度也促进了对runtime机制的学习和了解。

关于YYModel的分析到这儿先告一段落,由于本人的iOS基础有待提升,再加上表达能力有限,文中许多地方的分析和思路,表达的不是很准确和清楚,希望通过今后的学习和练习,提升自己的水平。

集合和数组的相互转化

区别:集合比数组多了一层{ }

   // 数组
    NSArray *array = [NSArray arrayWithObjects:@"CoderZb",@"fuqiang",@"minzhu",nil];
    NSLog(@"数组: %@",array);
    // 数组转成集合
    NSSet *set = [NSSet setWithArray:array];
    NSLog(@"集合: %@",set);

时时app平台注册网站 2

集合和数组的转化.png

NSObject YYModel.h

​ 分析NSObject YYModel.h文件,包括3个Category和一个protocol,分别是:

NSObject(YYModel)

NSArray(YYModel)

NSDictionary(YYModel)

YYModel <NSObject>

  1. NSObject(YYModel)

    负责扩展NSObject类,提供了Model相关方法,下面是代码注释:

    @interface NSObject (YYModel)
    //根据JSON对象创建Model
      (nullable instancetype)yy_modelWithJSON:(id)json;
    //根据NSDictionary创建Model
      (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
    //将JSON对象的各字段映射到Model的各个字段
    - (BOOL)yy_modelSetWithJSON:(id)json;
    //将NSDictionary的各字段映射到Model的各个字段
    - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
    //根据Model创建JSON对象
    - (nullable id)yy_modelToJSONObject;
    //根据Model创建JSON数据流
    - (nullable NSData *)yy_modelToJSONData;
    //根据Model创建JSON字符串
    - (nullable NSString *)yy_modelToJSONString;
    //复制Model
    - (nullable id)yy_modelCopy;
    //对Model进行归档
    - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder;
    //对Model进行解档
    - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder;
    //model的hash值
    - (NSUInteger)yy_modelHash;
    //判断Model和参数model是否相同
    - (BOOL)yy_modelIsEqual:(id)model;
    //输出Model的相关信息
    - (NSString *)yy_modelDescription;
    @end
    

    例如定义一个Student类,代码如下:

    @interface College : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    //Student
    @interface Student : NSObject <YYModel>
    @property (nonatomic, copy) NSString *name; //名字
    @property (nonatomic, assign) NSInteger age; //年龄
    @property (nonatomic, strong) College *college; //学校
    @end
    

    同时创建一个字典对象,如下:

    NSDictionary *studentDic = @{@"name" : @"Tomy",
                                 @"age" : @18,
                                 @"college" : @{@"name" : @"NJU"}};
    

    调用[Student yy_modelWithDictionary:studentDic]方法,创建一个Student类型的Model对象,然后将studentDic的每个value赋值给Model相应属性。property的名称和key一一对应。在转化的过程中,可以实现Model嵌套的情况,如Student中嵌套一个College对象。

    调用[Student yy_modelToJSONObject]方法,创建一个字典对象,各键值对中的key是Model相应属性的名称,value是属性值。在转化的过程中,也支持嵌套的情况。

  2. NSArray(YYModel)

    负责扩展NSArray类,下面是代码注释:

     @interface NSArray (YYModel)
     //根据JSON创建Model数组(JSON是数组格式)
       (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;
     @end
    

    例如有一个数组,如下:

    NSArray *studentArr = 
             @[@{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}}, 
         @{@"name" : @"Alex", @"age" : @19, @"college" : @{@"name" : @"Harvard"}},
         @{@"name" : @"Sunny", @"age" : @20, @"college" : @{@"name" : @"Yale"}}];
    

    调用[NSArray yy_modelArrayWithClass:Student json:studentArr]方法,创建一个Model数组,数组中的每个元素是Student对象。

  3. NSDictionary(YYModel)

    负责扩展NSDictionary类,下面是代码注释:

    @interface NSDictionary (YYModel)
    //根据JSON的value和Class对象创建Model,并创建一个新字典,取json的key作为key,取Model作为value
      (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
    @end
    

    例如创建一个字典,如下:

    NSDictionary *playerDic = 
      @{@"player1" : @{@"name" : @"Tomy",
                       @"age" : @18,
                       @"college" : @{@"name" : @"NJU"}},
      @"player2" : @{@"name" : @"Alex",
                     @"age" : @19,
                     @"college" : @{@"name" : @"Yale"}}};
    

    调用[NSDictionary yy_modelDictionaryWithClass:Student json:playerDic]得到一个新的字典studentDic,如下:

1-6.png

字典studentDic中的key对应原字典playerDic中的key,studentDic中的Model由playerDic中的value转化得到。

  1. @protocol YYModel

    YYModel是iOS协议,里面声明了一些方法,调用类通过实现这些方法,实现JSON和Model之间转换过程中的特殊处理。

      (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
      (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
      (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
      (nullable NSArray<NSString *> *)modelPropertyBlacklist;
      (nullable NSArray<NSString *> *)modelPropertyWhitelist;
    - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
    - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
    - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
    

    下面讲一下协议中的每个方法:

    1. modelCustomPropertyMapper方法,可以指定json和model转化过程中key的映射关系,如果存在以下的字典:

      NSDictionary *studentDic = @{@"NAME" : @"Tomy",
                                     @"AGE" : @{@"num":@18},
                                   @"college" : @{@"name" : @"NJU"}};
      

      如果需要将studentDic转化成Student类型的model,需要实现如下modelCustomPropertyMapper方法:

        (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper
      {
        return @{@"name" : @"NAME",
                       @"age" : @"AGE.num"};
      }
      

      返回的键值对中的key是Student的property名称,value是studentDic中的key。

    2. 如果Student中存在property是对象数组或者字典,实现modelContainerPropertyGenericClass方法,例如Student存在属性mobilePhones,维护一组MobilePhone对象

      @interface MobilePhone : NSObject
      @property (nonatomic, copy) NSString *brand;
      @property (nonatomic, assign) NSInteger phoneNumber;
      @end
      
      @interface Student : NSObject <YYModel>
      @property (nonatomic, copy) NSString *name;
      @property (nonatomic, assign) NSInteger age;
      @property (nonatomic, strong) College *college;
      @property (nonatomic, strong) NSArray *mobilePhones; //MobilePhone对象数组
      @end
      NSDictionary *studentDic = 
        @{@"name" : @"Tomy",
          @"age" : @18,
          @"college" : @{@"name" : @"NJU"},
          @"mobilePhones" : @[@{@"brand" : @"iphone",@"phoneNumber" : @123456},
                              @{@"brand" : @"HUAWEI",@"phoneNumber" : @123456}]};
      

      调用[Student yy_modelWithDictionary:studentDic]方法将studentDic中的mobilePhones转化成Student的mobilePhones属性,需要实现如下:

        (nullable NSDictionary<NSString*, id>*)modelContainerPropertyGenericClass
      {
          return @{@"mobilePhones" : [MobilePhone class]};
      }
      
    3. modelCustomClassForDictionary方法用于指定生成Model的类型,如果没有实现该方法,用默认的类型,例如Student实现modelCustomClassForDictionary,如下:

        (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
        if ([dictionary[@"name"] isEqualToString @"Graduated"]) {
            return [Graduated class];
            } else {
            return [self class];
            }
      }
      

      调用[Student yy_modelWithDictionary:studentDic]方法,如果studentDic[@"name"]等于@"Graduated",则不创建Student类型的对象,而是创建Graduated类型的对象。

    4. modelPropertyBlacklist方法和modelPropertyWhitelist方法的作用相反,如果实现了modelPropertyBlacklist,该方法返回一个数组,数组中的值指定了Model中不需要被赋值的property,例如代码如下:

        (nullable NSArray<NSString *> *)modelPropertyBlacklist
      {
          return @[@"age", @"college"];
      }
      

      则Student中的age和college属性不会被赋值。如果实现了modelPropertyWhitelist方法,该方法返回一个数组,数组中的值指定了Model中仅需要被赋值的property,例如代码如下:

        (nullable NSArray<NSString *> *)modelPropertyWhitelist
      {
          return @[@"age", @"college"];
      }
      

      则Student中只有age和college属性会被赋值。

    5. modelCustomWillTransformFromDictionary:方法作用于根据JSON创建Model对象之前,该方法可以把JSON字典转化成一个新的字典。

    6. modelCustomTransformFromDictionary:方法作用于根据JSON创建Model对象之后,该方法可以对生成的Model做一些处理。

    7. modelCustomTransformToDictionary:方法作用于根据Model对象创建JSON之后,该方法可以对生成的JSON字典做一些处理。

 

数组的遍历(四种方法)


1.第一种方法

NSArray *arr1=@[@"zhangbin",@"zb",@"ls",@"mz"];

//常规的遍历

 for (int i=0; i<arr1.count;  i) {
NSLog(@"arr1[%i]=%@",i,arr1[i]);

}

输出结果

2016-04-20 16:15:51.024 NSArray遍历[21205:1111570] arr1[0]=zhangbin
2016-04-20 16:15:51.025 NSArray遍历[21205:1111570] arr1[1]=zb
2016-04-20 16:15:51.025 NSArray遍历[21205:1111570] arr1[2]=ls
2016-04-20 16:15:51.026 NSArray遍历[21205:1111570] arr1[3]=mz

2.第二种方法

NSArray *arr2=@[@"zhangbin",@"zb",@"ls",@"mz"];

//for循环增强,实现遍历 敲入forin(中间无空格),按住回车,可自动显示for in的格式

//OC的数组可以增强for循环来遍历。逐个取出arr2中的元素,将取出的元素赋值给obj2.

//注意:obj2的类型不一定是NSObject,类型是由数组中元素的类型决定的。所以obj2的类型换成NSString类型也可以。

for (NSObject *obj2 in arr2) {

NSLog(@"obj2=%@",obj2);

}

输出结果

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj2=zhangbin

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj2=zb

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj2=ls

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj2=mz

3.第三种方法

NSArray *arr3=@[@"zhangbin",@"zb",@"ls",@"mz"];

//使用OC数组的迭代器来遍历

//每取出一个元素就会调用一次block,每次调用block都会将当前取出的元素和元素对应的所以传递给我们

//obj3就是当前取出的元素,idx就是当前元素对应的索引.

//stop用于控制什么时候停止遍历

[arr3 enumerateObjectsUsingBlock:^(id obj3,NSUInteger idx,BOOL *stop){

NSLog(@"obj3=%@,idx=%lu",obj3,idx);

}];

输出结果

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj3=zhangbin,idx=0

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj3=zb,idx=1

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj3=ls,idx=2

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj3=mz,idx=3

4.第四种方法

NSArray *arr4=@[@"zhangbin",@"zb",@"ls",@"mz"];

[arr4 enumerateObjectsUsingBlock:^(id obj4,NSUInteger idx,BOOL *stop){

if (idx==1) {//索引遍历到1时,就把YES赋值给stop,即停止遍历.后面的idx==2,idx==3不再遍历了。

*stop=YES;

}

NSLog(@"obj4=%@,idx=%lu",obj4,idx);

}];

输出结果

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj4=zhangbin,idx=0

20160420 16:15:51.026 NSArray遍历[21205:1111570] obj4=zb,idx=1

其它方法

YYModel还提供了一些工具方法,下面简单分析一下:

  1. ModelDescription方法,用来描述该Model对象的相关信息,一般用于log输出。该方法主要是根据Model对象的类型,做不同的处理:

    如果是一些简单类型,做一些处理后直接输出,下面是代码注释:

    case YYEncodingTypeNSNumber:
    case YYEncodingTypeNSDecimalNumber:
    case YYEncodingTypeNSDate:
    case YYEncodingTypeNSURL: {
     return [NSString stringWithFormat:@"%@",model];
    }
    

    如果是集合、数组或者字典类型,需要遍历其中每个元素,递归调用ModelDescription,生成每个元素的输出信息,拼接成一个字符串输出,例如是NSArray:

    case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: {
     NSArray *array = (id)model;
     NSMutableString *desc = [NSMutableString new];
         if (array.count == 0) {
             return [desc stringByAppendingString:@"[]"];
         } else {
             [desc appendFormat:@"[n"];
             for (NSUInteger i = 0, max = array.count; i < max; i  ) {
                 NSObject *obj = array[i];
                 [desc appendString:@"    "];
                     //生成每个元素的输出信息,拼接字符串
                     [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)];
                     [desc appendString:(i   1 == max) ? @"n" : @";n"];
                 }
                 [desc appendString:@"]"];
                 return desc;
             }
         }
    

    如果是自定义Model对象,则遍历对象中每个属性,获取与之对应的_YYModelPropertyMeta对象,进一步判断_YYModelPropertyMeta对象的类型,做不同的处理。如果属于NSObject类型,递归调用ModelDescription方法。代码如下:

    for (NSUInteger i = 0, max = properties.count; i < max; i  ) {
     _YYModelPropertyMeta *property = properties[i];
     NSString *propertyDesc;
     if (property->_isCNumber) {
         NSNumber *num = ModelCreateNumberFromProperty(model, property);
             propertyDesc = num.stringValue;
     } else {
             switch (property->_type & YYEncodingTypeMask) {
             case YYEncodingTypeObject: {
                 id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
                 propertyDesc = ModelDescription(v);
                     if (!propertyDesc) propertyDesc = @"<nil>";
                 } break;
     }
    }
    
  2. YYNSDateFromString方法,用来将字符串类型转化成NSDate类型。因为NSDate是OC的对象,而JSON数据格式中不存在日期时间类型,因此需要进行转化。通常是将具有固定格式的字符串转成NSDate类型,例如:"yyyy-MM-dd HH:mm:ss"。

    该方法的主要思路是创建不同类型的NSDateFormatter对象和一组block,然后根据传入的字符串参数的格式,执行相应的block,在block中再根据字符串参数调用相应的dateFormatter进行转化。代码注释如下:

    dispatch_once(&onceToken, ^{
      {
         NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
                formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
        ...
        blocks[19] = ^(NSString *string) {
                    if ([string characterAtIndex:10] == 'T') {
                        return [formatter1 dateFromString:string];
                    } else {
                        return [formatter2 dateFromString:string];
                    }
                };
        ...
        YYNSDateParseBlock parser = blocks[string.length]; //根据string选择相应的block
        if (!parser) return nil;
        return parser(string); //执行block,选取dateFormatter进行转化,输出NSDate对象
    }
    

类的转化

//    用泛型替代父类:
func valueIsOk<T: JSONModel>(anyClass: T) {

}

//    JSONCart 是 JSONModel的子类,传入JSONCart
self.valueIsOk(anyClass: item)

 

 

 


 

 

字典应用举例

    NSDictionary *dict3=@{@"Age":@"100",@"Name":@"CoderZb"};
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:dict3];
    NSLog(@"修改之前的字典中的数据: %@",dictionary);
    [dictionary setValue:@"18" forKey:@"Age"];
    NSLog(@"修改之后的字典中的数据: %@",dictionary);

时时app平台注册网站 3

字典举例.png


主要方法

下面分析几个重要的方法:

  1. yy_modelWithJSON:方法

    该方法首先调用_yy_dictionaryWithJSON:方法将JSON数据进行转化,如果JSON数据是NSString或者NSData格式,转化为字典对象,否则直接返回。然后调用yy_modelWithDictionary:方法,代码如下:

      (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
            ...
            Class cls = [self class];
             //1.metaWithClass
            _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
            if (modelMeta->_hasCustomClassFromDictionary) {
                cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
            }
            ...
            //2.根据json字典对新创建的model进行赋值
            NSObject *one = [cls new];
            if ([one yy_modelSetWithDictionary:dictionary]) return one;
            return nil;
        }
    

    该方法主要分为2个步骤:

  • 根据Class对象cls创建_YYModelMeta

  • 调用yy_modelSetWithDictionary:对model进行赋值,主要代码注释如下:

    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
               //1、遍历dic中的key和value,调用ModelSetWithDictionaryFunction方法进行属性赋值
           CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
          //2、如果_keyPathPropertyMetas存放有映射关系,调用ModelSetWithPropertyMetaArrayFunction方法进行赋值
           if (modelMeta->_keyPathPropertyMetas) {
               CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                         ModelSetWithPropertyMetaArrayFunction,
                                         &context);
            }
          //3、如果_multiKeysPropertyMetas存放有映射关系,调用ModelSetWithPropertyMetaArrayFunction方法进行赋值
            if (modelMeta->_multiKeysPropertyMetas) {
                    CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                         CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                         ModelSetWithPropertyMetaArrayFunction,
                                         &context);
                }
        } else {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                                     CFRangeMake(0, modelMeta->_keyMappedCount),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
        }
    

    主要分为2种情况:

    1. 如果Model中不存在属性名的映射,调用CFDictionaryApplyFunction方法遍历JSON字典中的每一个键值对,调用ModelSetWithDictionaryFunction方法对每一个键值对进行处理,由于_YYModelMeta内部维护了一个mapper字典,通过mapper取出YYModelPropertyMeta对象,最终调用ModelSetValueForProperty方法进行该属性的赋值。方法如下:

      static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
          ModelSetContext *context = _context;
          __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
          //根据key在mapper中查找对应的propertyMeta,如果没有,不进行后续操作
          __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
          __unsafe_unretained id model = (__bridge id)(context->model);
          //调用方法进行属性赋值
          while (propertyMeta) {
              if (propertyMeta->_setter) {
                  ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
              }
              propertyMeta = propertyMeta->_next;
          };
      }
      
    2. 如果Model中存在_keyPathPropertyMetas属性或者_multiKeysPropertyMetas属性,说明_keyPathPropertyMetas数组或者_multiKeysPropertyMetas数组中维护了一组Model中的属性,这些属性的名称存在映射关系,这种情况下调用CFArrayApplyFunction方法对keyPathPropertyMetas数组或者multiKeysPropertyMetas数组进行遍历,并调用ModelSetWithPropertyMetaArrayFunction方法对每个属性元素进行处理,该方法最终也会调用ModelSetValueForProperty方法进行属性赋值。代码注释如下:

      static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
          ...
          //根据映射关系找到JSON中的value
          if (propertyMeta->_mappedToKeyArray) {
              value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
          } else if (propertyMeta->_mappedToKeyPath) {
              value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
          } else {
              value = [dictionary objectForKey:propertyMeta->_mappedToKey];
          }
          //进行属性赋值
          if (value) {
              __unsafe_unretained id model = (__bridge id)(context->model);
              ModelSetValueForProperty(model, value, propertyMeta);
          }
      }
      

    上述两种情况最终会调用ModelSetValueForProperty方法在赋值的时候做了类型兼容,下文中具体分析。

  1. yy_modelToJSONObject方法

    该方法调用ModelToJSONObjectRecursive方法将Model对象转化成一个字典或者数组,需要转化的Model对象可以分为3类:

    第一类是一些基本类型,直接返回或者做一些处理返回,代码如下:

    if ([model isKindOfClass:[NSString class]]) return model;
    if ([model isKindOfClass:[NSNumber class]]) return model;
    if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
    if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
    

    第二类是NSDictionary、NSSet、NSArray等类型,它们不属于自定义的对象类型,代码如下:

    if ([model isKindOfClass:[NSDictionary class]]) { //如果是NSDictionary类型,遍历字典,用递归的方式赋值给新的字典,并返回新的字典。
         if ([NSJSONSerialization isValidJSONObject:model]) return model;
             NSMutableDictionary *newDic = [NSMutableDictionary new];
            [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
                NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
                if (!stringKey) return;
                id jsonObj = ModelToJSONObjectRecursive(obj);
                if (!jsonObj) jsonObj = (id)kCFNull;
                newDic[stringKey] = jsonObj;
            }];
            return newDic;
    }
    //如果是NSSet类型,遍历NSSet,用递归的方式赋值给新的set,并返回新的set。
    if ([model isKindOfClass:[NSSet class]]) {
     NSArray *array = ((NSSet *)model).allObjects;
         if ([NSJSONSerialization isValidJSONObject:array]) return array;
     NSMutableArray *newArray = [NSMutableArray new];
     for (id obj in array) {
     if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
         [newArray addObject:obj];
     } else {
         id jsonObj = ModelToJSONObjectRecursive(obj);
         if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
                }
            }
         return newArray;
    }
    //如果是NSArray类型,遍历NSArray,用递归的方式赋值给新的array,并返回新的array。
    if ([model isKindOfClass:[NSArray class]]) {
            if ([NSJSONSerialization isValidJSONObject:model]) return model;
            NSMutableArray *newArray = [NSMutableArray new];
            for (id obj in (NSArray *)model) {
                if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
                    [newArray addObject:obj];
                } else {
                    id jsonObj = ModelToJSONObjectRecursive(obj);
                    if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
                }
            }
            return newArray;
    }
         ...
    

    第三种是自定义的Model对象类型,首先调用metaWithClass获取自定义Model的_YYModelMeta对象,通过上文知道_YYModelMeta内部维护了一个mapper字典,用于维护Model内部的属性类型信息,然后遍历mapper中的每一个元素(_YYModelPropertyMeta),根据元素的类型,进行相应的处理,如下:

    [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
         if (propertyMeta->_isCNumber) { //数值类型
             value = ModelCreateNumberFromProperty(model, propertyMeta);
     } else if (propertyMeta->_nsType) {
             id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
             value = ModelToJSONObjectRecursive(v); //递归调用
         } else {
             ...
         }
    }]
    

    如果_YYModelPropertyMeta存在mappedToKeyPath属性,需要将属性名映射到指定的key上面。

  2. yy_modelArrayWithClass:json:方法

    该方法通过JSON数组和Class创建Model数组,首先调用yy_modelArrayWithClass: array:方法,然后在该方法中遍历数组,数组中每个元素是字典类型,调用yy_modelWithDictionary:方法进行转化,然后将转化后的Model添加进新数组中,最后返回这个新数组。代码如下:

      (NSArray *)yy_modelArrayWithClass:(Class)cls array:(NSArray *)arr {
        if (!cls || !arr) return nil;
        NSMutableArray *result = [NSMutableArray new];
        for (NSDictionary *dic in arr) { //遍历数组
            if (![dic isKindOfClass:[NSDictionary class]]) continue;
            NSObject *obj = [cls yy_modelWithDictionary:dic]; //dic->Model
            if (obj) [result addObject:obj]; //添加Model
        }
        return result;
    }
    
  3. yy_modelDictionaryWithClass:json:方法

    该方法和上面的方法3类似,首先调用yy_modelDictionaryWithClass:dictionary:方法,然后在该方法中遍历JSON字典,字典中的每个元素同样是字典,调用yy_modelWithDictionary:方法进行转化,然后将转化后的Model加入新的字典中,key是之前JSON字典中的key。代码如下:

      (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic {
        if (!cls || !dic) return nil;
        NSMutableDictionary *result = [NSMutableDictionary new];
        for (NSString *key in dic.allKeys) { //遍历字典
            if (![key isKindOfClass:[NSString class]]) continue;
            NSObject *obj = [cls yy_modelWithDictionary:dic[key]]; //dic[key]->Model
            if (obj) result[key] = obj; //添加Model
        }
        return result;
    }
    

字典转化

1 //    把 item 转化为字典可以这样做:
2 let dic = item as? [String: Any]
3 //    或者
4 let dic1 = item as? Dictionary[String: Any]

 

replaceObjectAtIndex:withObject:用法

NSMutableArray *array = @[@"ZZZ",@"BBB",@NO];
[array replaceObjectAtIndex:2 withObject:@YES];//  将下标为2的那个元素@NO替换成了@YES.

数组转化为Any类型

一个不知类型的数组变量 someArray

let dataArray =  (someArray as? Array<Any>)

或者这样

let dataArray = (someArray as? [Any])

 都可以的。

 

 


 

 

关于字典、数组书写格式的相关简写(超重点)


1.数组的简写、全写

NSArray *str6=@[@"zhangbin",@"zb",@"ls",@"mz"];//简写形式

NSArray *arr4=[NSArray arrayWithObjects:@"zhangbin",@"zb",@"ls",@"mz",nil];//全写形式

2.NSDictionary获取元素的简写(可变字典也同样适用)

dict[@"name"];//简写      精华:根据key获取value元素

[dict objectForKey:@"name"];//全写

3.修改字典中的数据

dicM[@"age"]=@"100"; //简写   相当于赋值

[dicM setObject:@"100" forKey:@"age"];// 全写

4.基本数据类型转换为对象类型的简写 判断对象类型:对象前面有*

int age=10;

NSNumber *temp3=@(age);//简写

NSLog(@"%@",temp3);

等价于

int age=10;

NSNumber *ageN=[NSNumber numberWithInt:age];//全写

NSLog(@"%@",ageN);

定义数组

1 //定义数组类型
2 
3 var array1: Array<String>
4 var array2:[String]

数组 字典综合运用


NSArray *BookData = @[   //6个字典

@{@"icon": @"HistoryBooks", @"name": @"历史书"},

@{@"icon": @"ChineseBooks", @"name": @"语文书"},

@{@"icon": @"MathBooks", @"name": @"数学书"},

@{@"icon": @"GeographyBooks", @"name": @"地理书"},

@{@"icon": @"BiologyBooks", @"name": @"生物书"},

@{@"icon": @"ChemistryBooks", @"name": @"化学书"}

];

// 将数组中的对象(字典)写入到plist文件

[shopData writeToFile:@"/Users/zhangbin/Desktop/未命名文件夹 2/zb.plist" atomically:YES];

// 读取plist文件中的对象(字典)

NSArray *newArray=[NSArray arrayWithContentsOfFile:@"/Users/zhangbin/Desktop/未命名文件夹 2/zb.plist"];

NSLog(@"%@",newArray);

}

@end

创建字典(简写繁写)


1.利用字典创建一个key value

NSDictionary *dict3=@{@"name":@"zb"};//创建字典

NSLog(@"%@",dict3[@"name"]);//获取字典中key对应的value

等价于

NSDictionary *dict1 = [NSDictionary dictionaryWithObject:@"zb" forKey:@"name"];//key是为了方便找到Object。类似于提供偏旁部首是为了快速找到汉字

NSString *name1 = [dict1 objectForKey:@"name"];

NSLog(@"name是%@",name1);

2.利用字典创建多个key value

NSDictionary *dict4=@{@"name":@"zb",@"age":@"23",@"height":@"1.70"};

NSLog(@"%@ %@ %@",dict4[@"name"],dict4[@"age"],dict4[@"height"]);

等价于

NSDictionary *dict2=[NSDictionary dictionaryWithObjects:@[@"zb",@"23",@"1.70"] forKeys:@[@"name",@"age",@"height"]];

NSLog(@"%@,%@,%@",[dict2 objectForKey:@"name"],[dict2 objectForKey:@"age"],[dict2 objectForKey:@"height"]);

将字典写入到plist文件,并且读取plist文件


// 写入到plist文件

NSDictionary *dict6=@{@"name":@"zb",@"age":@"23",@"height":@"1.70"};

[dict6 writeToFile:@"/Users/zhangbin/Documents/heihei.plist" atomically:YES];

// 读取plist文件

NSDictionary *newDict=[NSDictionary dictionaryWithContentsOfFile:@"/Users/zhangbin/Documents/heihei.plist"];

NSLog(@"%@",newDict);

valueforkey和objectforkey区别

1.objectforkey 是NSDictionary的方法,valueforkey 是KVC的方法

2.两者都是键值对应

3.区别是valueforkey只允许使用NSString类型,objectforkey可以是任意类型.

本文由时时app平台注册网站发布于编程知识,转载请注明出处:iOS源码分析—YYModel(NSObject YYModel卡塔尔【时时

关键词: