揭祕 YYModel 的魔法(下)

前言

在上文《揭祕 YYModel 的魔法(上)》 中主要剖析了 YYModel 的源碼結構,而且分享了 YYClassInfo 與 NSObject+YYModel 內部有趣的實現細節。javascript

緊接上篇,本文將解讀 YYModel 關於 JSON 模型轉換的源碼,旨在揭祕 JSON 模型自動轉換魔法。html

索引

  • JSON 與 Model 相互轉換
  • 總結

JSON 與 Model 相互轉換

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,它易於人們閱讀和編寫,同時也易於機器解析和生成。它是基於 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999 的一個子集。JSON 採用徹底獨立於語言的文本格式,可是也使用了相似於 C 語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使 JSON 成爲理想的數據交換語言,點擊 這裏 瞭解更多關於 JSON 的信息。java

Model 是 面向對象編程(Object Oriented Programming,簡稱 OOP)程序設計思想中的對象,OOP 把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。通常咱們會根據業務需求來建立對象,在一些設計模式中(如 MVC 等)對象通常做爲模型(Model),即對象建模。git

JSON 與 Model 相互轉換按轉換方向分爲兩種:github

  • JSON to Model
  • Model to JSON

JSON to Model

咱們從 YYModel 的接口開始解讀。編程

+ (instancetype)yy_modelWithJSON:(id)json {
    // 將 json 轉爲字典 dic
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    // 再經過 dic 獲得 model 並返回
    return [self yy_modelWithDictionary:dic];
}
複製代碼

上面接口把 JSON 轉 Model 很簡單的分爲了兩個子任務:json

  • JSON to NSDictionary
  • NSDictionary to Model

JSON to NSDictionary

咱們先看一下 _yy_dictionaryWithJSON 是怎麼將 json 轉爲 NSDictionary 的。設計模式

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    // 入參判空
    if (!json || json == (id)kCFNull) return nil;
    
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    // 根據 json 的類型對應操做
    if ([json isKindOfClass:[NSDictionary class]]) {
        // 若是是 NSDictionary 類則直接賦值
        dic = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        // 若是是 NSString 類則用 UTF-8 編碼轉 NSData
        jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        // 若是是 NSData 則直接賦值給 jsonData
        jsonData = json;
    }
    
    // jsonData 不爲 nil,則表示上面的 二、3 狀況中的一種
    if (jsonData) {
        // 利用 NSJSONSerialization 方法將 jsonData 轉爲 dic
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        // 判斷轉換結果 
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    
    return dic;
}
複製代碼

這個函數主要是根據入參的類型判斷如何將其轉爲 NSDictionary 類型並返回。數組

其中 kCFNull 是 CoreFoundation 中 CFNull 的單例對象。如同 Foundation 框架中的 NSNull 同樣,CFNull 是用來表示集合對象中的空值(不容許爲 NULL)。CFNull 對象既不容許被建立也不容許被銷燬,而是經過定義一個 CFNull 常量,即 kCFNull,在須要空值時使用。xcode

官方文檔: The CFNull opaque type defines a unique object used to represent null values in collection objects (which don’t allow NULL values). CFNull objects are neither created nor destroyed. Instead, a single CFNull constant object—kCFNull—is defined and is used wherever a null value is needed.

NSJSONSerialization 是用於將 JSON 和等效的 Foundation 對象之間相互轉換的對象。它在 iOS 7 以及 macOS 10.9(包含 iOS 7 和 macOS 10.9)以後是線程安全的。

代碼中將 NSString 轉爲 NSData 用到了 NSUTF8StringEncoding,其中編碼類型必須屬於 JSON 規範中列出的 5 種支持的編碼類型:

  • UTF-8
  • UTF-16LE
  • UTF-16BE
  • UTF-32LE
  • UTF-32BE

而用於解析的最高效的編碼是 UTF-8 編碼,因此做者這裏使用 NSUTF8StringEncoding。

官方註釋: The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.

NSDictionary to Model

如今咱們要從 yy_modelWithJSON 接口中探究 yy_modelWithDictionary 是如何將 NSDictionary 轉爲 Model 的。

敲黑板!作好準備,這一小節介紹的代碼是 YYModel 的精華哦~。

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    // 入參校驗
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    
    // 使用當前類生成一個 _YYModelMeta 模型元類
    Class cls = [self class];
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    // 這裏 _hasCustomClassFromDictionary 用於標識是否須要自定義返回類
    // 屬於模型轉換附加功能,能夠不用投入太多關注
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    // 調用 yy_modelSetWithDictionary 爲新建的類實例 one 賦值,賦值成功則返回 one
    NSObject *one = [cls new];
    // 因此這個函數中咱們應該把注意力集中在 yy_modelSetWithDictionary
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    
    return nil;
}
複製代碼

代碼中根據 _hasCustomClassFromDictionary 標識判斷是否須要自定義返回模型的類型。這段代碼屬於 YYModel 的附加功能,爲了避免使你們分心,這裏僅作簡單介紹。

若是咱們要在 JSON 轉 Model 的過程當中根據狀況建立不一樣類型的實例,則能夠在 Model 中實現接口:

+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
複製代碼

來知足需求。當模型元初始化時會檢測當前模型類是否能夠響應上面的接口,若是能夠響應則會把 _hasCustomClassFromDictionary 標識爲 YES,因此上面纔會出現這些代碼:

if (modelMeta->_hasCustomClassFromDictionary) {
    cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
複製代碼

嘛~ 我以爲這些附加的東西在閱讀源碼時很大程度上會分散咱們的注意力,此次先詳細的講解一下,之後遇到相似的代碼咱們會略過,內部的實現大都與上述案例原理相同,感興趣的同窗能夠本身研究哈。

咱們應該把注意力集中在 yy_modelSetWithDictionary 上,這個函數(其實也是 NSObject+YYModel 暴露的接口)是根據字典初始化模型的實現方法。它的代碼比較長,若是不想看能夠跳過,在後面有解釋。

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    // 入參校驗
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    
    // 根據自身類生成 _YYModelMeta 模型元類
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    // 若是模型元類鍵值映射數量爲 0 則 return NO,表示構建失敗
    if (modelMeta->_keyMappedCount == 0) return NO;
    
    // 忽略,該標識對應 modelCustomWillTransformFromDictionary 接口
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        // 該接口相似 modelCustomTransformFromDictionary 接口,不過是在模型轉換以前調用的
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    
    // 初始化模型設置上下文 ModelSetContext
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    // 判斷模型元鍵值映射數量與 JSON 所得字典的數量關係
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        // 通常狀況下他們的數量相等
        // 特殊狀況好比有的屬性元會映射字典中的多個 key
        
        // 爲字典中的每一個鍵值對調用 ModelSetWithDictionaryFunction
        // 這句話是核心代碼,通常狀況下就是靠 ModelSetWithDictionaryFunction 經過字典設置模型
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        // 判斷模型中是否存在映射 keyPath 的屬性元
        if (modelMeta->_keyPathPropertyMetas) {
            // 爲每一個映射 keyPath 的屬性元執行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        // 判斷模型中是否存在映射多個 key 的屬性元
        if (modelMeta->_multiKeysPropertyMetas) {
            // 爲每一個映射多個 key 的屬性元執行 ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else { // 模型元鍵值映射數量少,則認爲不存在映射多個 key 的屬性元
        // 直接爲每一個 modelMeta 屬性元執行 ModelSetWithPropertyMetaArrayFunction
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    // 忽略,該標識對應接口 modelCustomTransformFromDictionary
    if (modelMeta->_hasCustomTransformFromDictionary) {
        // 該接口用於當默認 JSON 轉 Model 不適合模型對象時作額外的邏輯處理
        // 咱們也能夠用這個接口來驗證模型轉換的結果
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    
    return YES;
}
複製代碼

代碼已經註明必要中文註釋,關於兩處自定義擴展接口咱們再也不多說,因爲代碼比較長咱們先來梳理一下 yy_modelSetWithDictionary 主要作了哪些事?

  • 入參校驗
  • 初始化模型元以及映射表校驗
  • 初始化模型設置上下文 ModelSetContext
  • 爲字典中的每一個鍵值對調用 ModelSetWithDictionaryFunction
  • 檢驗轉換結果

模型設置上下文 ModelSetContext 其實就是一個包含模型元,模型實例以及待轉換字典的結構體。

typedef struct {
    void *modelMeta;  ///< 模型元
    void *model;      ///< 模型實例,指向輸出的模型
    void *dictionary; ///< 待轉換字典
} ModelSetContext;
複製代碼

你們確定都注意到了 ModelSetWithDictionaryFunction 函數,不論走哪條邏輯分支,最後都是調用這個函數把字典的 key(keypath)對應的 value 取出並賦值給 Model 的,那麼咱們就來看看這個函數的實現。

// 字典鍵值對建模
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    // 拿到入參上下文
    ModelSetContext *context = _context;
    // 取出上下文中模型元
    __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
    // 根據入參 _key 從模型元中取出映射表對應的屬性元
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    // 拿到待賦值模型
    __unsafe_unretained id model = (__bridge id)(context->model);
    // 遍歷 propertyMeta,直到 propertyMeta->_next == nil
    while (propertyMeta) {
        // 當前遍歷的 propertyMeta 有 setter 方法,則調用 ModelSetValueForProperty 賦值
        if (propertyMeta->_setter) {
            // 核心方法,拎出來說
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}
複製代碼

ModelSetWithDictionaryFunction 函數的實現邏輯就是先經過模型設置上下文拿到帶賦值模型,以後遍歷當前的屬性元(直到 propertyMeta->_next == nil),找到 setter 不爲空的屬性元經過 ModelSetValueForProperty 方法賦值。

ModelSetValueForProperty 函數是爲模型中的屬性賦值的實現方法,也是整個 YYModel 的核心代碼。別緊張,這個函數寫得很友好的,也就 300 多行而已 😜(可有可無的內容我會盡可能忽略掉),不過忽略的太多會影響代碼閱讀的連續性,若是嫌長能夠不看,文章後面會總結一下這個函數的實現邏輯。

static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) {
    // 若是屬性是一個 CNumber,即輸入 int、uint……
    if (meta->_isCNumber) {
        // 轉爲 NSNumber 以後賦值
        NSNumber *num = YYNSNumberCreateFromID(value);
        // 這裏 ModelSetNumberToProperty 封裝了給屬性元賦值 NSNumber 的操做
        ModelSetNumberToProperty(model, num, meta);
        if (num) [num class]; // hold the number
    } else if (meta->_nsType) {
        // 若是屬性屬於 nsType,即 NSString、NSNumber……
        if (value == (id)kCFNull) { // 爲空,則賦值 nil(經過屬性元 _setter 方法使用 objc_msgSend 將 nil 賦值)
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
        } else { // 不爲空
            switch (meta->_nsType) {
                // NSString 或 NSMutableString
                case YYEncodingTypeNSString:
                case YYEncodingTypeNSMutableString: {
                    // 處理可能的 value 類型:NSString,NSNumber,NSData,NSURL,NSAttributedString
                    // 對應的分支就是把 value 轉爲 NSString 或者 NSMutableString,以後調用 setter 賦值
                    ...
                } break;
                
                // NSValue,NSNumber 或 NSDecimalNumber
                case YYEncodingTypeNSValue:
                case YYEncodingTypeNSNumber:
                case YYEncodingTypeNSDecimalNumber: {
                    // 對屬性元的類型分狀況賦值(中間可能會涉及到類型之間的轉換)
                    ...
                } break;
                    
                // NSData 或 NSMutableData
                case YYEncodingTypeNSData:
                case YYEncodingTypeNSMutableData: {
                    // 對屬性元的類型分狀況賦值(中間可能會涉及到類型之間的轉換)
                    ...
                } break;
                    
                // NSDate
                case YYEncodingTypeNSDate: {
                    // 考慮可能的 value 類型:NSDate 或 NSString
                    // 轉換爲 NSDate 以後賦值
                    ...
                } break;
                    
                // NSURL
                case YYEncodingTypeNSURL: {
                    // 考慮可能的 value 類型:NSURL 或 NSString
                    // 轉換爲 NSDate 以後賦值(這裏對 NSString 的長度判斷是否賦值 nil)
                    ...
                } break;
                    
                // NSArray 或 NSMutableArray
                case YYEncodingTypeNSArray:
                case YYEncodingTypeNSMutableArray: {
                    // 對屬性元的泛型判斷
                    if (meta->_genericCls) { // 若是存在泛型
                        NSArray *valueArr = nil;
                        // value 所屬 NSArray 則直接賦值,若是所屬 NSSet 類則轉爲 NSArray
                        if ([value isKindOfClass:[NSArray class]]) valueArr = value;
                        else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
                        
                        // 遍歷剛纔經過 value 轉換來的 valueArr
                        if (valueArr) {
                            NSMutableArray *objectArr = [NSMutableArray new];
                            for (id one in valueArr) {
                                // 遇到 valueArr 中的元素屬於泛型類,直接加入 objectArr
                                if ([one isKindOfClass:meta->_genericCls]) {
                                    [objectArr addObject:one];
                                } else if ([one isKindOfClass:[NSDictionary class]]) {
                                    // 遇到 valueArr 中的元素是字典類,
                                    Class cls = meta->_genericCls;
                                    // 忽略
                                    if (meta->_hasCustomClassFromDictionary) {
                                        cls = [cls modelCustomClassForDictionary:one];
                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                    }
                                    // 還記得咱們直接的起點 yy_modelSetWithDictionary,將字典轉模型
                                    // 我以爲這應該算是一個間接遞歸調用
                                    // 若是設計出的模型是無限遞歸(從前有座山,山上有座廟的故事),那麼確定會慢
                                    NSObject *newOne = [cls new];
                                    [newOne yy_modelSetWithDictionary:one];
                                    // 轉化成功機也加入 objectArr
                                    if (newOne) [objectArr addObject:newOne];
                                }
                            }
                            // 最後將獲得的 objectArr 賦值給屬性
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
                        }
                    } else {
                        // 沒有泛型,嘛~ 判斷一下 value 的可能所屬類型 NSArray 或 NSSet
                        // 轉換賦值(涉及 mutable)
                        ...
                    }
                } break;
                
                // NSDictionary 或 NSMutableDictionary
                case YYEncodingTypeNSDictionary:
                case YYEncodingTypeNSMutableDictionary: {
                    // 跟上面數組的處理超類似,泛型的間接遞歸以及無泛型的類型轉換(mutable 的處理)
                    ...
                } break;
                    
                // NSSet 或 NSMutableSet
                case YYEncodingTypeNSSet:
                case YYEncodingTypeNSMutableSet: {
                    // 跟上面數組的處理超類似,泛型的間接遞歸以及無泛型的類型轉換(mutable 的處理)
                    ...
                }
                
                default: break;
            }
        }
    } else { // 屬性元不屬於 CNumber 和 nsType 
        BOOL isNull = (value == (id)kCFNull);
        switch (meta->_type & YYEncodingTypeMask) {
            // id
            case YYEncodingTypeObject: {
                if (isNull) { // 空,賦值 nil
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
                    // 屬性元與 value 從屬於同一個類,則直接賦值
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
                } else if ([value isKindOfClass:[NSDictionary class]]) {
                    // 嘛~ value 從屬於 
                    NSObject *one = nil;
                    // 若是屬性元有 getter 方法,則經過 getter 獲取到實例
                    if (meta->_getter) {
                        one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                    }
                    if (one) {
                        // 用 yy_modelSetWithDictionary 輸出化屬性實例對象
                        [one yy_modelSetWithDictionary:value];
                    } else {
                        Class cls = meta->_cls;
                        // 略過
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary:value];
                            if (!cls) cls = meta->_genericCls; // for xcode code coverage
                        }
                        // 用 yy_modelSetWithDictionary 輸出化屬性實例對象,賦值
                        one = [cls new];
                        [one yy_modelSetWithDictionary:value];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                    }
                }
            } break;
            
            // Class
            case YYEncodingTypeClass: {
                if (isNull) { // 空,賦值(Class)NULL,因爲 Class 實際上是 C 語言定義的結構體,因此使用 NULL
                    // 關於 nil,Nil,NULL,NSNull,kCFNull 的橫向比較,我會單獨拎出來在下面介紹
                    ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
                } else {
                    // 判斷 value 可能的類型 NSString 或判斷 class_isMetaClass(object_getClass(value))
                    // 若是知足條件則賦值
                    ...
                }
            } break;
            
            // SEL
            case  YYEncodingTypeSEL: {
                // 判空,賦值(SEL)NULL
                // 不然轉換類型 SEL sel = NSSelectorFromString(value); 而後賦值
                ...
            } break;
                
            // block
            case YYEncodingTypeBlock: {
                // 判空,賦值(void (^)())NULL
                // 不然判斷類型 [value isKindOfClass:YYNSBlockClass()] 以後賦值
                ...
            } break;
            
            // struct、union、char[n],關於 union 共同體感興趣的同窗能夠本身 google,這裏簡單介紹一下
            // union 共同體,相似 struct 的存在,可是 union 每一個成員會用同一個存儲空間,只能存儲最後一個成員的信息
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion:
            case YYEncodingTypeCArray: {
                if ([value isKindOfClass:[NSValue class]]) { 
                    // 涉及 Type Encodings
                    const char *valueType = ((NSValue *)value).objCType;
                    const char *metaType = meta->_info.typeEncoding.UTF8String;
                    // 比較 valueType 與 metaType 是否相同,相同(strcmp(a, b) 返回 0)則賦值
                    if (valueType && metaType && strcmp(valueType, metaType) == 0) {
                        [model setValue:value forKey:meta->_name];
                    }
                }
            } break;
            
            // void* 或 char*
            case YYEncodingTypePointer:
            case YYEncodingTypeCString: {
                if (isNull) { // 判空,賦值(void *)NULL
                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
                } else if ([value isKindOfClass:[NSValue class]]) {
                    // 涉及 Type Encodings
                    NSValue *nsValue = value;
                    if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) {
                        ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
                    }
                }
            }
                
            default: break;
        }
    }
}
複製代碼

額 😓 我是真的已經忽略掉不少代碼了,沒辦法仍是有點長。其實代碼邏輯仍是很簡單的,只是模型賦值涉及的編碼類型等瑣碎邏輯比較多致使代碼量比較大,咱們一塊兒來總結一下核心代碼的實現邏輯。

  • 根據屬性元類型劃分代碼邏輯
  • 若是屬性元是 CNumber 類型,即 int、uint 之類,則使用 ModelSetNumberToProperty 賦值
  • 若是屬性元屬於 NSType 類型,即 NSString、NSNumber 之類,則根據類型轉換中可能涉及到的對應類型作邏輯判斷並賦值(能夠去上面代碼中查看具體實現邏輯)
  • 若是屬性元不屬於 CNumber 和 NSType,則猜想爲 id,Class,SEL,Block,struct、union、char[n],void* 或 char* 類型而且作出相應的轉換和賦值

嘛~ 其實上面的代碼除了長之外邏輯仍是很簡單的,總結起來就是根據可能出現的類型去作出對應的邏輯操做,建議各位有時間仍是去讀下源碼,尤爲是本身項目中用到 YYModel 的同窗。相信看完以後會對 YYModel 屬性賦值一清二楚,這樣在使用 YYModel 的平常中出現任何問題均可以心中有數,改起代碼天然若有神助哈。

額...考慮到 NSDictionary to Model 的整個過程代碼量不小,我花了一些時間將其邏輯總結概括爲一張圖:

但願能夠盡本身的努力讓文章的表述變得更直白。

Model to JSON

相比於 JSON to Model 來講,Model to JSON 更簡單一些。其中由於 NSJSONSerialization 在對 JSON 的轉換時作了一些規定:

  • 頂級對象是 NSArray 或者 NSDictionary 類型
  • 全部的對象都是 NSString, NSNumber, NSArray, NSDictionary, 或 NSNull 的實例
  • 全部字典中的 key 都是一個 NSString 實例
  • Numbers 是除去無窮大和 NaN 的其餘表示

Note: 上文出自 NSJSONSerialization 官方文檔

知道了這一點後,咱們就能夠從 YYModel 的 Model to JSON 接口 yy_modelToJSONObject 處開始解讀源碼了。

- (id)yy_modelToJSONObject {
    // 遞歸轉換模型到 JSON
    id jsonObject = ModelToJSONObjectRecursive(self);
    if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
    if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
    
    return nil;
}
複製代碼

嘛~ 一共 4 行代碼,只須要關注一下第一行代碼中的 ModelToJSONObjectRecursive 方法,Objective-C 的語言特性決定了從函數名稱便可無需註釋看懂代碼,這個方法從名字上就能夠 get 到它是經過遞歸方法使 Model 轉換爲 JSON 的。

// 遞歸轉換模型到 JSON,若是轉換異常則返回 nil
static id ModelToJSONObjectRecursive(NSObject *model) {
    // 判空或者能夠直接返回的對象,則直接返回
    if (!model || model == (id)kCFNull) return model;
    if ([model isKindOfClass:[NSString class]]) return model;
    if ([model isKindOfClass:[NSNumber class]]) return model;
    // 若是 model 從屬於 NSDictionary
    if ([model isKindOfClass:[NSDictionary class]]) {
        // 若是能夠直接轉換爲 JSON 數據,則返回
        if ([NSJSONSerialization isValidJSONObject:model]) return model;
        NSMutableDictionary *newDic = [NSMutableDictionary new];
        // 遍歷 model 的 key 和 value
        [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
            NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
            if (!stringKey) return;
            // 遞歸解析 value 
            id jsonObj = ModelToJSONObjectRecursive(obj);
            if (!jsonObj) jsonObj = (id)kCFNull;
            newDic[stringKey] = jsonObj;
        }];
        return newDic;
    }
    // 若是 model 從屬於 NSSet
    if ([model isKindOfClass:[NSSet class]]) {
        // 若是可以直接轉換 JSON 對象,則直接返回
        // 不然遍歷,按須要遞歸解析
        ...
    }
    if ([model isKindOfClass:[NSArray class]]) {
        // 若是可以直接轉換 JSON 對象,則直接返回
        // 不然遍歷,按須要遞歸解析
        ...
    }
    // 對 NSURL, NSAttributedString, NSDate, NSData 作相應處理
    if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
    if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
    if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
    if ([model isKindOfClass:[NSData class]]) return nil;
    
    // 用 [model class] 初始化一個模型元
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
    // 若是映射表爲空,則不作解析直接返回 nil
    if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
    // 性能優化細節,使用 __unsafe_unretained 來避免在下面遍歷 block 中直接使用 result 指針形成的沒必要要 retain 與 release 開銷
    NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
    __unsafe_unretained NSMutableDictionary *dic = result;
    // 遍歷模型元屬性映射字典
    [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        // 若是遍歷當前屬性元沒有 getter 方法,跳過
        if (!propertyMeta->_getter) return;
        
        id value = nil;
        // 若是屬性元屬於 CNumber,即其 type 是 int、float、double 之類的
        if (propertyMeta->_isCNumber) {
            // 從屬性中利用 getter 方法獲得對應的值
            value = ModelCreateNumberFromProperty(model, propertyMeta);
        } else if (propertyMeta->_nsType) { // 屬性元屬於 nsType,即 NSString 之類
            // 利用 getter 方法拿到 value
            id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
            // 對拿到的 value 遞歸解析
            value = ModelToJSONObjectRecursive(v);
        } else {
            // 根據屬性元的 type 作相應處理
            switch (propertyMeta->_type & YYEncodingTypeMask) {
                // id,須要遞歸解析,若是解析失敗則返回 nil
                case YYEncodingTypeObject: {
                    id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = ModelToJSONObjectRecursive(v);
                    if (value == (id)kCFNull) value = nil;
                } break;
                // Class,轉 NSString,返回 Class 名稱
                case YYEncodingTypeClass: {
                    Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromClass(v) : nil;
                } break;
                // SEL,轉 NSString,返回給定 SEL 的字符串表現形式
                case YYEncodingTypeSEL: {
                    SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromSelector(v) : nil;
                } break;
                default: break;
            }
        }
        // 若是 value 仍是沒能解析,則跳過
        if (!value) return;
        
        // 當前屬性元是 KeyPath 映射,即 a.b.c 之類
        if (propertyMeta->_mappedToKeyPath) {
            NSMutableDictionary *superDic = dic;
            NSMutableDictionary *subDic = nil;
            // _mappedToKeyPath 是 a.b.c 根據 '.' 拆分紅的字符串數組,遍歷 _mappedToKeyPath
            for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
                NSString *key = propertyMeta->_mappedToKeyPath[i];
                // 遍歷到結尾
                if (i + 1 == max) {
                    // 若是結尾的 key 爲 nil,則使用 value 賦值
                    if (!superDic[key]) superDic[key] = value;
                    break;
                }
                
                // 用 subDic 拿到當前 key 對應的值
                subDic = superDic[key];
                // 若是 subDic 存在
                if (subDic) {
                    // 若是 subDic 從屬於 NSDictionary
                    if ([subDic isKindOfClass:[NSDictionary class]]) {
                        // 將 subDic 的 mutable 版本賦值給 superDic[key]
                        subDic = subDic.mutableCopy;
                        superDic[key] = subDic;
                    } else {
                        break;
                    }
                } else {
                    // 將 NSMutableDictionary 賦值給 superDic[key]
                    // 注意這裏使用 subDic 間接賦值是有緣由的,緣由就在下面
                    subDic = [NSMutableDictionary new];
                    superDic[key] = subDic;
                }
                // superDic 指向 subDic,這樣在遍歷 _mappedToKeyPath 時便可逐層解析
                // 這就是上面先把 subDic 轉爲 NSMutableDictionary 的緣由
                superDic = subDic;
                subDic = nil;
            }
        } else {
            // 若是不是 KeyPath 則檢測 dic[propertyMeta->_mappedToKey],若是爲 nil 則賦值 value
            if (!dic[propertyMeta->_mappedToKey]) {
                dic[propertyMeta->_mappedToKey] = value;
            }
        }
    }];
    
    // 忽略,對應 modelCustomTransformToDictionary 接口
    if (modelMeta->_hasCustomTransformToDictionary) {
        // 用於在默認的 Model 轉 JSON 過程不適合當前 Model 類型時提供自定義額外過程
        // 也能夠用這個方法來驗證轉換結果
        BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
        if (!suc) return nil;
    }
    
    return result;
}
複製代碼

額...代碼仍是有些長,不過相比於以前 JSON to Model 方向上由 yy_modelSetWithDictionaryModelSetWithDictionaryFunctionModelSetValueForProperty 三個方法構成的間接遞歸來講算是很是簡單了,那麼總結一下上面的代碼邏輯。

  • 判斷入參,若是知足條件能夠直接返回
  • 若是 Model 從屬於 NSType,則根據不一樣的類型作邏輯處理
  • 若是上面條件不被知足,則用 Model 的 Class 初始化一個模型元 _YYModelMeta
  • 判斷模型元的映射關係,遍歷映射表拿到對應鍵值對並存入字典中並返回

Note: 這裏有一個性能優化的細節,用 __unsafe_unretained 修飾的 dic 指向咱們最後要 return 的 NSMutableDictionary *result,看做者的註釋:// avoid retain and release in block 是爲了不直接使用 result 在後面遍歷映射表的代碼塊中沒必要要的 retain 和 release 操做以節省開銷。

總結

  • 文章緊接上文《揭祕 YYModel 的魔法(上)》中對 YYModel 代碼結構的講解後將重點放到了對 JSON 模型相互轉換的實現邏輯上。
  • 從 JSON 模型的轉換方向上劃分,將 YYModel 的 JSON 模型轉換過程正反方向剖析揭祕,但願能夠解開你們對 JSON 模型自動轉換的疑惑。

文章寫的比較用心(是我我的的原創文章,轉載請註明 lision.me/),若是發現錯誤會優先在個人 我的博客 中更新。

若是對文章有哪些意見能夠直接在個人微博 @Lision 聯繫我(由於社區發文以後的通知太多了,因此我把這些 push 給關了只留了微博,微博冷清~~~~(>_<)~~~~)。


補充~ 我建了一個技術交流微信羣,想在裏面認識更多的朋友!若是各位同窗對文章有什麼疑問或者工做之中遇到一些小問題均可以在羣裏找到我或者其餘羣友交流討論,期待你的加入喲~

Emmmmm..因爲微信羣人數過百致使不能夠掃碼入羣,因此請掃描上面的二維碼關注公衆號進羣。

相關文章
相關標籤/搜索