1. 程式人生 > >iOS中字典轉模型的方法及底層原理

iOS中字典轉模型的方法及底層原理

1 自動列印屬性字串分類

  • 提供一個分類,專門根據字典生成對應的屬性字串。
@implementation NSObject (Property)

+ (void)PH_createPropertyCodeWithDict:(NSDictionary *)dict
{
    NSMutableString *strM = [NSMutableString string];
    // 遍歷字典
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull propertyName, id  _Nonnull value, BOOL * _Nonnull stop) {
        NSLog
(@"%@,%@",propertyName,[value class]); NSString *code; if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) { code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",propertyName] ; } else if ([value isKindOfClass:NSClassFromString
(@"__NSCFNumber")]){ code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName] ; }else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){ code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;"
,propertyName] ; }else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){ code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName] ; } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){ code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName] ; } [strM appendFormat:@"\n%@\n",code]; }]; NSLog(@"%@",strM); } @end

2 字典轉模型的方式一:KVC

@implementation Status


+ (instancetype)statusWithDict:(NSDictionary *)dict
{
    Status *status = [[self alloc] init];

    [status setValuesForKeysWithDictionary:dict];

    return status;

}

@end
  • KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。
    • 如果不一致,就會呼叫[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
      key找不到的錯。
    • 分析:模型中的屬性和字典的key不一一對應,系統就會呼叫setValue:forUndefinedKey:報錯。
    • 解決:重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋,
      就能繼續使用KVC,字典轉模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{

}

3 字典轉模型的方式二:Runtime

  • KVC:遍歷字典中所有key,去模型中查詢有沒有對應的屬性名
  • runtime:遍歷模型中所有屬性名,去字典中查詢

1.思路與步驟

<!--* 思路:利用執行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查詢key,取出對應的值,給模型的屬性賦值。-->
<!--* 步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類轉。-->

2.分類

+ (instancetype)PH_modelWithDict:(NSDictionary *)dict
{
    // 建立對應類的物件
    id objc = [[self alloc] init];

    // runtime:遍歷模型中所有成員屬性,去字典中查詢
    // 屬性定義在哪,定義在類,類裡面有個屬性列表(陣列)

    // 遍歷模型所有成員屬性
    // ivar:成員屬性
    // class_copyIvarList:把成員屬性列表複製一份給你
    // Ivar *:指向Ivar指標
    // Ivar *:指向一個成員變數陣列
    // class:獲取哪個類的成員屬性列表
    // count:成員屬性總數

    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 獲取成員屬性
        Ivar ivar = ivarList[i];
        // 獲取成員名
        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //成員屬性型別
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        //獲取key
        NSString *key = [propertyName substringFromIndex:1];
        //獲取字典的value
        id value = dict[key];
        // 給模型的屬性賦值
        // value:字典的值
        // key:屬性名

// user:NSDictionary
        //** '二級轉換'**
          // 值是字典,成員屬性的型別不是字典,才需要轉換成模型

        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
            // 需要字典轉換成模型
            // 轉換成哪個型別

            // @"@\"User\"" User
            NSRange range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            // User\"";

            range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location];
            // 字串擷取

            // 獲取需要轉換類的類物件
            Class modelClass = NSClassFromString(propertyType);
            if (modelClass) {
                value = [modelClass PH_modelWithDict:value];
            }

        }

<!-- 三級轉換:NSArray中也是字典,把陣列中的字典轉換成模型.-->
        // 判斷值是否是陣列
        if ([value isKindOfClass:[NSArray class]]) {
            //判斷對應類有沒有實現字典陣列轉模型陣列的協議
            if ([self respondsToSelector:@selector(PH_arrayContainModelClass)]) {

                // 轉換成id型別,就能呼叫任何物件的方法
                id idSelf = self;

                // 獲取陣列中字典對應的模型
                NSString *type =  [idSelf PH_arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];

                // 遍歷字典陣列,生成模型陣列
                for (NSDictionary *dict in value) {
                    // 字典轉模型
                    id model = [classModel PH_modelWithDict:dict];
                    [arrM addObject:model];
                }
                // 把模型陣列賦值給value
                value = arrM;
            }
        }


        if (value) {
            // kvc賦值:不能傳空
            [objc setValue:value forKey:key];
        }
        NSLog(@"%@",key);
        NSLog(@"%@,%@",propertyName,propertyType);
    }

    return objc;
}

3.轉換

- (void)viewDidLoad {
    [super viewDidLoad];

    // 解析
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *dictArr = dict[@"statuses"];

    // 設計模型屬性程式碼

    NSMutableArray *statuses = [NSMutableArray array];

    for (NSDictionary *dict in dictArr) {
        // 字典轉模型
        Status *status = [Status PH_modelWithDict:dict];
        [statuses addObject:status];
    }
    NSLog(@"%@",statuses);

}

KVC: Key Value Coding (鍵值編碼)

在iOS開發中,KVC是我們經常要使用的技術.那麼KVC有什麼作用呢?簡單列舉一下下面幾種:

  • 取值和賦值(開發中基本不用)
  • 獲取物件私有變數的值.(經常使用,例如UIPageContorl分頁, 設定圓點為圖片)
  • 改變物件私有變數的值(經常使用)
  • 簡單的字典轉模型(偶爾使用)
  • 模型轉字典
  • 批量取值

KVC字典轉模型的底層實現

  • 通常我們手動將字典轉模型的話,會在模型中提供一個類方法接收一個字典,在這個方法中將字典轉換成模型,再將轉換好的模型返回.

    + (instancetype)statusWithDict:(NSDictionary *)dict
    {
      Status *status = [[self alloc] init];
      //利用KVC字典轉模型
      [status setValuesForKeysWithDictionary:dict];
    
      return status;
    }
  • 分析一下[status setValuesForKeysWithDictionary:dict]的底層實現原理

    + (instancetype)statusWithDict:(NSDictionary *)dict
    {
      Status *status = [[self alloc] init];
      //利用KVC字典轉模型
      //[status setValuesForKeysWithDictionary:dict];
    
      //setValuesForKeysWithDictionary:原理--遍歷字典中所有的key,去模型中查詢對應的屬性,把值給模型屬性賦值
    
      [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
          // 這行程式碼才是真正給模型的屬性賦值
          [status setValue:obj forKey:key];
      }];
      return status;
    }
  • KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。如果不是一一對應的話,就會報錯,仔細看一下錯誤資訊,[<Status 0x7fd439d20a60> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key source.是系統呼叫了setValue:forUndefinedKey:報錯.如果想解決這個問題,只需要在模型中重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋, 就能繼續使用KVC,字典轉模型了。
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
    }

囉嗦一點KVC的setValue:forKey:方法賦值的原理

  • 首先會去模型中查詢有沒有對應key的setter方法,有就直接呼叫set方法方法賦值.
  • 上一步沒有的話,去模型中查詢有沒有和key同名的屬性,有的話賦值給與key同名的屬性.
  • 上一步還沒有的話,去屬性中查詢有沒有和key同名的帶下劃線的屬性,有的話直接賦值.
  • 如果再沒有,那就直接呼叫物件的 setValue:forUndefinedKey:直接報錯