溫馨提示×

溫馨提示×

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

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

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

發布時間:2020-07-11 22:20:23 來源:網絡 閱讀:4639 作者:Owenli_千 欄目:移動開發

前言

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

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

目錄

  • JSON -> Model
  • Model -> JSON

JSON轉Model

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

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

+ (instancetype)yy_modelWithJSON:(id)json;

注意jsonid類型,接收三種不同類型參數NSString,NSData,NSDictionay。下面是內部實現:

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

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

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    // 驗證json
    if (!json || json == (id)kCFNull) return nil;
    // 兩個臨時變量
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    // 根據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轉為字典
    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:集合對象中,表示空值的對象。如給數組設置空值,使用NSNull,而不能使用nil。

kCFNullNSNull的單例。

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

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

獲取到字典后,調用字典轉Model方法:

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

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

/**
 字典轉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. 創建一個YYModelMeta對象
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    // 判斷是否需要自定義返回模型的類型,這是YYModel協議中的內容,算是附加功能暫時先忽略,后面在介紹。
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    // 3. 創建model對象
    NSObject *one = [cls new];

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

    return nil;
}

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

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

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

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

        // 調用ModelSetWithDictionaryFunction方法,這是核心方法。
        // 參數:
        // 1. 要操作的字典
        // 2. 為每個鍵值對調用一次的回調函數
        // 3. 指針大小的程序定義的值,作為第三個參數傳遞給應用程序函數,但此函數未使用該值. 與參數2適應
        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) {
            // 每個keypath都執行ModelSetWithPropertyMetaArrayFunction
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }

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

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

  • 驗證參數(為了程序健壯性一定要這么做)
  • 創建YYModelMeta對象
  • 創建ModelSetContext結構體
  • 為字典的每個鍵值對調用ModelSetWithDictionaryFunction方法
  • 驗證結果

關于ModelSetContext是一個結構體。包含模型元,模型實例和待處理字典。

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);
    // 核心內容,遍歷所有的屬性元。知道_next = nil
    while (propertyMeta) {
        if (propertyMeta->_setter) {
                // 最終轉換(高潮)
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}

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

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

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

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

調用ModelSetNumberToPorperty方法,該方法作用是:識別null、bool、123.23、"123.45"等類型,轉為NSNumber。

以上就是JSON轉Model全部過程。

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

Model轉JSON

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

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

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

官方說明NSJSONSerialization文檔

接下來看看是如何轉換的,首先看到的方法是:

- (id)yy_modelToJSONObject;

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

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

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

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

總結

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

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

個人博客Owenli
微博Owenli_千

向AI問一下細節

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

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女