溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

從YYModel源碼中可以學到什么:后篇

發(fā)布時間:2020-07-11 22:20:23 來源:網(wǎng)絡 閱讀:4599 作者:Owenli_千 欄目:移動開發(fā)

前言

上一篇中《從YYModel源碼中可以學到什么:后篇》中主要學習了YYModel的源碼結(jié)構(gòu),只是分享了YYModel整體結(jié)構(gòu)。

承接上篇,本文將解讀YYModel如何進行JSON模型轉(zhuǎn)換的,接下來一起揭開YYModel的神秘面紗吧!

目錄

  • JSON -> Model
  • Model -> JSON

JSON轉(zhuǎn)Model

從YYModel源碼中可以學到什么:后篇

首先來看JSON是如何轉(zhuǎn)為Model。查看YYModel的接口,提供了一個方法:

+ (instancetype)yy_modelWithJSON:(id)json;

注意jsonid類型,接收三種不同類型參數(shù)NSString,NSData,NSDictionay。下面是內(nèi)部實現(xiàn):

+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self yy_modelWithDictionary:dic];
}

方法中調(diào)用了一個私有方法_yy_dictionaryWithJSON:,該方法將id類型的json(NSDictionary, NSString, NSData)轉(zhuǎn)為字典。
從YYModel源碼中可以學到什么:后篇

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    // 驗證json
    if (!json || json == (id)kCFNull) return nil;
    // 兩個臨時變量
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    // 根據(jù)json類型,進行相應處理
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = json;
    }
    // 使用NSJSONSerialization將Data轉(zhuǎn)為字典
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    return dic;
}

該方法很簡單,就是判斷id類型是指(NSDictionary,NSString,NSData)中的哪一個,分別處理。

json == (id)kCFNullkCFNull是什么意思?這條語句起到什么作用?

nil: 指向OC中對象的空指針

Nil: 指向OC中類的空指針

NULL:定義其他類型(基本類型、C類型)的空指針

NSNull:集合對象中,表示空值的對象。如給數(shù)組設置空值,使用NSNull,而不能使用nil。

kCFNullNSNull的單例。

if (!json || json == (id)kCFNull) return nil;該判斷的意思是,json對象不存在,或者為空是返回nil。

從YYModel源碼中可以學到什么:后篇

獲取到字典后,調(diào)用字典轉(zhuǎn)Model方法:

+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary

此方法也是在.h文件中暴露出來的方法。接下來查看具體實現(xiàn):

/**
 字典轉(zhuǎn)model

 @param dictionary 字典
 @return 返回Model對象
 */
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    // 1. 驗證,空值,nil和是否是字典
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;

    Class cls = [self class];
    // 2. 創(chuàng)建一個YYModelMeta對象
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    // 判斷是否需要自定義返回模型的類型,這是YYModel協(xié)議中的內(nèi)容,算是附加功能暫時先忽略,后面在介紹。
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    // 3. 創(chuàng)建model對象
    NSObject *one = [cls new];

    // 4. 關(guān)鍵方法
    if ([one yy_modelSetWithDictionary:dictionary])
        return one;

    return nil;
}

這個方法主要的任務是調(diào)用了yy_modelSetWithDictionary:方法。這個方法也是在.h文件中暴露的,它的作用是根據(jù)字典初始化模型。代碼實現(xiàn):

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    // 1. 值和類型驗證
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;

    // 2. 創(chuàng)建Modelmeta對象
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    // 屬性個數(shù),如為零返回。創(chuàng)建失敗
    if (modelMeta->_keyMappedCount == 0) return NO;
    // YYModel協(xié)議,暫且忽略
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    // 3. 創(chuàng)建結(jié)構(gòu)體
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);

    // 模型元值數(shù)量和字典數(shù)量關(guān)系
    // 1. 通常情況是兩者相等,
    // 2. 模型元鍵值少于字典個數(shù)
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {

        // 調(diào)用ModelSetWithDictionaryFunction方法,這是核心方法。
        // 參數(shù):
        // 1. 要操作的字典
        // 2. 為每個鍵值對調(diào)用一次的回調(diào)函數(shù)
        // 3. 指針大小的程序定義的值,作為第三個參數(shù)傳遞給應用程序函數(shù),但此函數(shù)未使用該值. 與參數(shù)2適應
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);

        // 是否存在映射keyPath屬性元
        if (modelMeta->_keyPathPropertyMetas) {
            // 每個keypath都執(zhí)行ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        // 是否存在映射多個key的屬性元
        if (modelMeta->_multiKeysPropertyMetas) {
            // 每個keypath都執(zhí)行ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }

    } else {
        // 每個keypath都執(zhí)行ModelSetWithPropertyMetaArrayFunction
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    // 忽略
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

上面注釋該方法大致干了幾件事。

  • 驗證參數(shù)(為了程序健壯性一定要這么做)
  • 創(chuàng)建YYModelMeta對象
  • 創(chuàng)建ModelSetContext結(jié)構(gòu)體
  • 為字典的每個鍵值對調(diào)用ModelSetWithDictionaryFunction方法
  • 驗證結(jié)果

關(guān)于ModelSetContext是一個結(jié)構(gòu)體。包含模型元,模型實例和待處理字典。

typedef struct {
    void *modelMeta;  ///< _YYModelMeta
    void *model;      ///< id (self)
    void *dictionary; ///< NSDictionary (json)
} ModelSetContext;

通過上面的分析,線路越來越清晰,下面看一下核心方法ModelSetWithDictionaryFunction將字典的鍵值對取出賦值給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);
    // 核心內(nèi)容,遍歷所有的屬性元。知道_next = nil
    while (propertyMeta) {
        if (propertyMeta->_setter) {
                // 最終轉(zhuǎn)換(高潮)
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

該方法主要任務是,遍歷所有的屬性元,調(diào)用模型屬性賦值方法ModelSetValueForProperty。

最終實現(xiàn)。也就是YYModel的最最最核心的部分。不過,這個方法長的離譜。由于涉及到較多的編碼類型,需要對不同的類型區(qū)分處理,導致代碼過長。

由于ModelSetValueForPorperty代碼較長,這里不再復制代碼。只梳理一下實現(xiàn)的邏輯,注釋代碼在Github自行查閱。

  • 元類型,isCNumber, nsType和其他類型來區(qū)分處理。
  • isCNumber此時調(diào)用方法ModelSetNumberToPorperty。該方法將NSNumber類型的值根據(jù)不同的編碼賦值給屬性。
  • nsTypeYYEncodingNSType枚舉類型,枚舉Foundation所有的類型,對不同的類型進行處理。
  • 最后是除上述的其他情況。YYEncodingType枚舉定義的情況。

調(diào)用ModelSetNumberToPorperty方法,該方法作用是:識別null、bool、123.23、"123.45"等類型,轉(zhuǎn)為NSNumber。

以上就是JSON轉(zhuǎn)Model全部過程。

從YYModel源碼中可以學到什么:后篇

Model轉(zhuǎn)JSON

從YYModel源碼中可以學到什么:后篇

相對JSON轉(zhuǎn)Model來說,Model轉(zhuǎn)JSON就簡單多了??梢允褂?code>NSJSONSerialization類將Foundation對象轉(zhuǎn)為JSON。但是轉(zhuǎn)為JSON必須滿足一下條件。

  • 頂層對象必須是NSArrayNSDictionary
  • 所有對象都是實例NSString,NSNumber,NSArray,NSDictionary或者NSNull
  • 所有字典鍵都是實例NSString
  • 數(shù)字不是NAN或無窮大

官方說明NSJSONSerialization文檔

接下來看看是如何轉(zhuǎn)換的,首先看到的方法是:

- (id)yy_modelToJSONObject;

該方法的時下很簡單只有幾行代碼。

- (id)yy_modelToJSONObject {
    // 關(guān)鍵方法
    id jsonObject = ModelToJSONObjectRecursive(self);
    if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
    if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
    return nil;
}

內(nèi)部調(diào)用了方法ModelToJSONObjectRecursive方法。作者對該方法注釋中說,返回一個有效的JSON對象(NSArray/NSDictionary/NSString/NSumber/NSNull)或者為空。查看源碼,該方法做了幾件事

  • 驗證參數(shù)
  • 根據(jù)參數(shù)類型進行處理,NSString, NSNumber直接返回數(shù)據(jù);字典,集合,數(shù)組類型,需要遍歷所有元素遞歸處理;最后處理對象類型。
  • 返回不同類型的處理結(jié)果

總結(jié)

承接上文,本文對JSON轉(zhuǎn)Model主線路進行詳細的分析,關(guān)于一些輔助功能沒有分析。還有,一些代碼比較長,由于篇幅限制,所以注釋代碼放在《GitHub》。

由于筆者能力有限,不可避免的出現(xiàn)錯誤,歡迎大家指正。

個人博客Owenli
微博Owenli_千

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI