YYModel 學習探究

YYModel是一款很是好用且很是輕量級的JSON模型轉換庫,源碼一共就五個文件,去掉聲明文件,全部的實現邏輯都在NSObject+YYModel.mYYClassInfo.m這兩個文件中,如圖: ios

本文從YYModel的代碼結構,YYModel的使用,實現JSON轉模型的源碼邏輯三個方面入手,藉此加深對runtime和KVC的深刻理解;

1、YYModel的代碼結構

YYClassInfo功能主要是將Runtime層級中的一些api(結構體封)裝到NSObject子類中,將runtime的api對象化,方便調用;NSObject+YYModel是提供調用的接口以及實現具體的模型轉換邏輯,YYModel的代碼結構如圖: git

一、YYClassInfo中對runtime的Api封裝類

  • 1.一、YYClassIvarInfoobjc_ivar封裝對比json

    • YYClassIvarInfo 對象聲明:
      @interface YYClassIvarInfo : NSObject
      @property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct
      @property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name @property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset
      @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding @property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type
      - (instancetype)initWithIvar:(Ivar)ivar;
      @end
      複製代碼
    • Runtime中objc_ivar的定義:
      struct objc_ivar {
          char * _Nullable ivar_name OBJC2_UNAVAILABLE; // 變量名稱
          char * _Nullable ivar_type OBJC2_UNAVAILABLE; // 變量類型
          int ivar_offset OBJC2_UNAVAILABLE; // 變量偏移量
      #ifdef __LP64__ // 若是已定義 __LP64__ 則表示正在構建 64 位目標
          int space OBJC2_UNAVAILABLE; // 變量空間
      #endif
      }
      複製代碼
  • 1.二、YYClassMethodInfoobjc_method封裝對比api

    • YYClassMethodInfo對象聲明:數組

      @interface YYClassMethodInfo : NSObject
      @property (nonatomic, assign, readonly) Method method; ///< 方法
      @property (nonatomic, strong, readonly) NSString *name; ///< 方法名稱
      @property (nonatomic, assign, readonly) SEL sel; ///< 方法選擇器
      @property (nonatomic, assign, readonly) IMP imp; ///< 方法實現,指向實現方法函數的函數指針
      @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< 方法參數和返回類型編碼
      @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< 返回值類型編碼
      @property (nullable, nonatomic, strong, readonly) NSArray<nsstring *> *argumentTypeEncodings; ///< 參數類型編碼數組
      - (instancetype)initWithMethod:(Method)method;
      @end
      複製代碼
    • Runtime中objc_method的定義:緩存

      struct objc_method {
          SEL _Nonnull method_name OBJC2_UNAVAILABLE; // 方法名稱
          char * _Nullable method_types OBJC2_UNAVAILABLE; // 方法類型
          IMP _Nonnull method_imp OBJC2_UNAVAILABLE; // 方法實現(函數指針)
      }
      複製代碼
  • 1.三、YYClassPropertyInfoproperty_t封裝對比安全

    • YYClassPropertyInfo對象聲明:bash

      @interface YYClassPropertyInfo : NSObject
      @property (nonatomic, assign, readonly) objc_property_t property; ///< 屬性
      @property (nonatomic, strong, readonly) NSString *name; ///< 屬性名稱
      @property (nonatomic, assign, readonly) YYEncodingType type; ///< 屬性類型
      @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< 屬性類型編碼
      @property (nonatomic, strong, readonly) NSString *ivarName; ///< 變量名稱
      @property (nullable, nonatomic, assign, readonly) Class cls; ///< 類型
      @property (nullable, nonatomic, strong, readonly) NSArray<nsstring *> *protocols; ///< 屬性相關協議
      @property (nonatomic, assign, readonly) SEL getter; ///< getter 方法選擇器
      @property (nonatomic, assign, readonly) SEL setter; ///< setter 方法選擇器
      - (instancetype)initWithProperty:(objc_property_t)property;
      複製代碼
    • Runtime中property_t的定義:網絡

      struct property_t {
          const char *name; // 名稱
          const char *attributes; // 修飾
      };
      複製代碼
  • 1.四、YYClassInfoobjc_class封裝對比數據結構

    • YYClassInfo對象聲明:
      @interface YYClassInfo : NSObject
      @property (nonatomic, assign, readonly) Class cls; ///< 類
      @property (nullable, nonatomic, assign, readonly) Class superCls; ///< 超類
      @property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< 元類
      @property (nonatomic, readonly) BOOL isMeta; ///< 元類標識,自身是否爲元類
      @property (nonatomic, strong, readonly) NSString *name; ///< 類名稱
      @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< 父類(超類)信息
      @property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclassivarinfo *> *ivarInfos; ///< 變量信息
      @property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclassmethodinfo *> *methodInfos; ///< 方法信息
      @property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclasspropertyinfo *> *propertyInfos; ///< 屬性信息
      - (void)setNeedUpdate;
      - (BOOL)needUpdate;
      + (nullable instancetype)classInfoWithClass:(Class)cls;
      + (nullable instancetype)classInfoWithClassName:(NSString *)className;
      @end
      複製代碼
    • Runtime中objc_class的定義:
      // objc.h
      typedef struct objc_class *Class;
      // runtime.h
      struct objc_class {
          Class _Nonnull isa OBJC_ISA_AVAILABILITY; // isa 指針
      #if !__OBJC2__
          Class _Nullable super_class OBJC2_UNAVAILABLE; // 父類(超類)指針
          const char * _Nonnull name OBJC2_UNAVAILABLE; // 類名
          long version OBJC2_UNAVAILABLE; // 版本
          long info OBJC2_UNAVAILABLE; // 信息
          long instance_size OBJC2_UNAVAILABLE; // 初始尺寸
          struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 變量列表
          struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; // 方法列表
          struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 緩存
          struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 協議列表
      #endif
      } OBJC2_UNAVAILABLE;
      複製代碼

二、NSObject+YYModel 數據結構的定義

  • NSObject+YYModel 數據結構的定義
    • _YYModelPropertyMeta 對象的聲明定義:
      @interface _YYModelPropertyMeta : NSObject {
          @package
          NSString *_name;             ///< 屬性名稱
          YYEncodingType _type;        ///< 屬性類型
          YYEncodingNSType _nsType;    ///< 屬性在 Foundation 框架中的類型
          BOOL _isCNumber;             ///< 是否爲 CNumber
          Class _cls;                  ///< 屬性類
          Class _genericCls;           ///< 屬性包含的泛型類型,沒有則爲 nil
          SEL _getter;                 ///< getter
          SEL _setter;                 ///< setter
          BOOL _isKVCCompatible;       ///< 若是可使用 KVC 則返回 YES
          BOOL _isStructAvailableForKeyedArchiver; ///< 若是可使用 archiver/unarchiver 歸/解檔則返回 YES
          BOOL _hasCustomClassFromDictionary; ///< 類/泛型自定義類型,例如須要在數組中實現不一樣類型的轉換須要用到
          /*
           property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
           property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
           property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
           */
          NSString *_mappedToKey;      ///< 映射 key
          NSArray *_mappedToKeyPath;   ///< 映射 keyPath,若是沒有映射到 keyPath 則返回 nil
          NSArray *_mappedToKeyArray;  ///< key 或者 keyPath 的數組,若是沒有映射多個鍵的話則返回 nil
          YYClassPropertyInfo *_info;  ///< 屬性信息,詳見上文 YYClassPropertyInfo && property_t 章節
          _YYModelPropertyMeta *_next; ///< 若是有多個屬性映射到同一個 key 則指向下一個模型屬性元
      }
      @end
      複製代碼
    • _YYModelMeta 對象的聲明定義:
      @interface _YYModelMeta : NSObject {
          @package
          YYClassInfo *_classInfo;
          /// Key:被映射的 key 與 keyPath, Value:_YYModelPropertyMeta.
          NSDictionary *_mapper;
          /// Array<_YYModelPropertyMeta>, 當前模型的全部 _YYModelPropertyMeta 數組
          NSArray *_allPropertyMetas;
          /// Array<_YYModelPropertyMeta>, 被映射到 keyPath 的 _YYModelPropertyMeta 數組
          NSArray *_keyPathPropertyMetas;
          /// Array<_YYModelPropertyMeta>, 被映射到多個 key 的 _YYModelPropertyMeta 數組
          NSArray *_multiKeysPropertyMetas;
          /// 映射 key 與 keyPath 的數量,等同於 _mapper.count
          NSUInteger _keyMappedCount;
          /// 模型 class 類型
          YYEncodingNSType _nsType;
          ///做用:判斷YYModel一系列協議方法是否實現
          BOOL _hasCustomWillTransformFromDictionary;//解析前是否須要更改字典
          BOOL _hasCustomTransformFromDictionary;//字典轉模型後是否須要補充處理
          BOOL _hasCustomTransformToDictionary;//模型轉字典後是否須要補充處理
          BOOL _hasCustomClassFromDictionary;//是否須要根據dic的內容轉換爲不一樣類型的模型
      }
      @end
      複製代碼

2、YYModel的具體使用 --- NSObject+YYModel 提供調用的接口

一、JSON數據轉換爲model實體

  • 1.json數據轉model數據, + (nullable instancetype)yy_modelWithJSON:(id)json; ,內部調用2;
  • 2.NSDictionary數據轉model數據, + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;,內部調用4;
    LGModel *model = [LGModel yy_modelWithDictionary:dict];
    複製代碼
  • 3.json數據爲model對象賦值,- (BOOL)yy_modelSetWithJSON:(id)json;,內部調用4;
  • 4.NSDictionary數據爲model對象賦值,- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;,json轉model的最終實現方法;
    LGModel *model1 = [[LGModel alloc]init];
    [model1 yy_modelSetWithDictionary:dict];
    複製代碼

二、model數據轉換爲JSON數據

  • 1.model轉爲json數據,- (nullable id)yy_modelToJSONObject;,model數據轉換爲JSON數據,最終實現
  • 2.model轉爲NSData,- (nullable NSData *)yy_modelToJSONData;,內部調用1
  • 3.model轉爲json字符串,- (nullable NSString *)yy_modelToJSONString;,內部調用1

三、其餘方法

  • 對象深拷貝 - (nullable id)yy_modelCopy;
  • 對象數據持久化存儲
    • 存:- (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder;
    • 取:- (id)yy_modelInitWithCoder:(NSCoder *)aDecoder;
  • 對象hash值 - (NSUInteger)yy_modelHash;
  • 對象是否相等 - (BOOL)yy_modelIsEqual:(id)model;
  • 對象描述 - (NSString *)yy_modelDescription;

四、集合相應方法

  • json-array集體實例化
    /**
     Creates and returns an array from a json-array.
     This method is thread-safe.
     @param cls  The instance's class in array. @param json A json array of `NSArray`, `NSString` or `NSData`. Example: [{"name","Mary"},{name:"Joe"}] @return A array, or nil if an error occurs. */ + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json; 複製代碼
  • json-object集體實例化
    /**
     Creates and returns a dictionary from a json.
     This method is thread-safe.
     @param cls  The value instance's class in dictionary. @param json A json dictionary of `NSDictionary`, `NSString` or `NSData`. Example: {"user1":{"name","Mary"}, "user2": {name:"Joe"}} @return A dictionary, or nil if an error occurs. */ + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; 複製代碼

五、協議方法 --- 用來處理映射中的各類問題

5.一、Model 屬性名和 JSON 中的 Key 不相同 + (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
//返回一個 Dict,將 Model 屬性名對映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"messageId":@[@"id",@"ID",@"book_id"]};
}
複製代碼
  • 你能夠把一個或一組json key (key path)映射到一個或多個屬性。若是一個屬性沒有映射關係,那默認會使用相同屬性名做爲映射。
  • 在 json->model 的過程當中:若是一個屬性對應了多個json key,那麼轉換過程會按順序查找,並使用第一個不爲空的值。
  • 在 model->json 的過程當中:若是一個屬性對應了多個 json key (key path),那麼轉換過程僅會處理第一個 json key (key path);若是多個屬性對應了同一個 json key,則轉換過過程會使用其中任意一個不爲空的值。
5.二、自定義容器中的實體類型映射 + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
// 返回容器類中的所須要存放的數據類型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass{
return @{@"books" : LGSubModel.class,
             @"infoDict" : [LGPerson class],
             @"likedUserIds" : @"NSNumber"
             };
}
複製代碼
  • 在實際使用過過程當中,[LGPerson class]LGPerson.class@"LGPerson"沒有明顯的區別。
5.三、根據dic來實例不一樣類的類型 + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
@implementation LGPerson
+(Class)modelCustomClassForDictionary:(NSDictionary *)dictionary {
    if ([dictionary[@"gender"] integerValue] == 1) {
        return LGMan.class;
    }
    return self;
}
@end
複製代碼
5.四、黑名單(不處理的屬性)+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
5.五、白名單(只處理的屬性) + (nullable NSArray<NSString *> *)modelPropertyWhitelist;
// 若是實現了該方法,則處理過程當中會忽略該列表內的全部屬性
+(NSArray<NSString *> *)modelPropertyBlacklist {
    return @[@"subject"];
}
// 若是實現了該方法,則處理過程當中不會處理該列表外的屬性
+ (NSArray<NSString *> *)modelPropertyWhitelist {
    return @[@"name",@"age",@"num"];
}
複製代碼
5.六、解析前更改字典信息 - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;,發生在字典轉模型以前,最後對網絡字典作一次處理;
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic{
    if ([dic[@"gender"] integerValue] == 1) {
        return nil;//不接受男性
    }
    return dic;
}
複製代碼
5.七、數據校驗與自定義轉換,字典轉模型補充,- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;,YYModel沒法處理或處理後格式類型等不正確,能夠在這裏從新賦值處理;
// 當 JSON 轉爲 Model 完成後,該方法會被調用。
// 你能夠在這裏對數據進行校驗,若是校驗不經過,能夠返回 NO,則該 Model 會被忽略。
// 你也能夠在這裏作一些自動轉換不能完成的工做。
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic{
   NSNumber *interval = dic[@"timeInterval"];
    if (![interval isKindOfClass:[NSNumber class]]) {
        return NO;
    }
    _createTime = [NSDate dateWithTimeIntervalSince1970:[interval floatValue]];
    return YES;
}
複製代碼
5.八、數據校驗與自定義轉換,模型轉字典補充,- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;,一樣,轉爲json時同樣有格式或類型不正確,能夠在這裏從新賦值處理;
// 當 Model 轉爲 JSON 完成後,該方法會被調用。
// 你能夠在這裏對數據進行校驗,若是校驗不經過,能夠返回 NO,則該 Model 會被忽略。
// 你也能夠在這裏作一些自動轉換不能完成的工做。
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
    if (!_createTime) {
        return NO;
    }
    dic[@"timeInterval"] = @([_createTime timeIntervalSince1970]) ;
    return YES;
}
複製代碼

3、YYModel實現json轉model的源碼邏輯

YYModel調用邏輯流程圖:

一、YYModel使用yy_modelWithJSON做爲JSON模型轉換的入口,將傳入的對象轉換成字典,調用yy_modelWithDictionary:,這個方法的內部實現以下:

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    /// 獲取當前模型類
    Class cls = [self class];
    ///二、經過class 獲取到各類信息,而後封裝到_YYModelMeta中
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    ///是否須要根據字典內容修改模型類
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    ///模型類實例化
    NSObject *one = [cls new];
    ///三、實現JSON轉模型功能
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}
複製代碼

二、metaWithClass:經過class 獲取到各類信息,而後封裝到_YYModelMeta中,返回緩存的_YYModelMeta信息,實現以下:

/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) return nil;
    ///聲明緩存模型類和類信息的字典,key爲類名
    static CFMutableDictionaryRef cache;
    static dispatch_once_t onceToken;
    ///保證線程安全鎖的聲明
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{///保證緩存字典只實例化一次
        ///實例化緩存字典
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        ///鎖建立
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);///鎖啓用
    ///以類名爲key從緩存取類信息_YYModelMeta
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);///鎖關閉
    if (!meta || meta->_classInfo.needUpdate) {
        ///2.一、緩存未取到類信息meta,根據model類cls去實例化該類信息
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);///鎖啓用
            ///緩存類信息meta,類名爲key
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);///鎖關閉
        }
    }
    return meta;
}
複製代碼
2.一、緩存未取到類信息meta,根據model類cls去實例化該類信息, initWithClass:方法有點長,其實現以下:
- (instancetype)initWithClass:(Class)cls {
    ///2.1.一、從Class中獲取類信息,並封裝成對象
    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    if (!classInfo) return nil;
    self = [super init];
    // Get black list-獲取黑名單
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    // Get white list-獲取白名單
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    // Get container property’s generic class - 返回容器類中的所須要存放的數據類型
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {/// key是字符串,obj是容器中存放的實例的類對象
                if (![key isKindOfClass:[NSString class]]) return;//key必須是字符串類型
                ///獲取容器中存放對象的類的元類
                Class meta = object_getClass(obj);
                if (!meta) return;
                if (class_isMetaClass(meta)) {
                    //處理[LGPerson class],LGPerson.class 兩種類型
                    tmp[key] = obj;
                } else if ([obj isKindOfClass:[NSString class]]) {
                    //處理@"LGPerson"類型
                    Class cls = NSClassFromString(obj);
                    if (cls) {
                        tmp[key] = cls;
                    }
                }
            }];
            genericMapper = tmp;//容器類屬性以及對應Class 的字典
        }
    }
    // Create all property metas.-獲取全部要解析的屬性,包括排除黑名單、驗證白名單、驗證是否有getter 和setter 等
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    YYClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)-遞歸解析父類,但忽略根類
        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;//不在白名單內不處理
            ///2.1.二、實例化_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;
        }
        curClassInfo = curClassInfo.superClassInfo;
    }///獲得全部要解析的屬性信息集合
    if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
    // create mapper
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    ///屬性名和json中的鍵不同的,爲屬性設置json中的key或者keyPath
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
            _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            //判斷屬性名對應的_YYModelPropertyMeta實例是否存在
            if (!propertyMeta) return;
            //移除屬性名對應的_YYModelPropertyMeta實例
            [allPropertyMetas removeObjectForKey:propertyName];
            //處理屬性名和json中的鍵一一對應的狀況
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) return;
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                for (NSString *onePath in keyPath) {
                    if (onePath.length == 0) {
                        NSMutableArray *tmp = keyPath.mutableCopy;
                        [tmp removeObject:@""];
                        keyPath = tmp;
                        break;
                    }
                }
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;//爲json中的鍵賦值
                ///處理屬性名對應json中的多個鍵
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in ((NSArray *)mappedToKey)) {
                    if (![oneKey isKindOfClass:[NSString class]]) continue;
                    if (oneKey.length == 0) continue;
                    NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
                    if (keyPath.count > 1) {
                        [mappedToKeyArray addObject:keyPath];
                    } else {
                        [mappedToKeyArray addObject:oneKey];
                    }
                    if (!propertyMeta->_mappedToKey) {
                        propertyMeta->_mappedToKey = oneKey;
                        propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
                    }
                }
                if (!propertyMeta->_mappedToKey) return;
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;//爲json中的鍵賦值
            }
        }];
    }
    ///將allPropertyMetas中剩下的值添加到mapper中
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    if (mapper.count) _mapper = mapper;//屬性賦值
    if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;//屬性賦值
    if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;//屬性賦值
    _classInfo = classInfo;//屬性賦值
    _keyMappedCount = _allPropertyMetas.count;//屬性賦值
    _nsType = YYClassGetNSType(cls);//屬性賦值
    _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);//解析前更改字典方法是否實現
    _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);//字典轉模型後補充處理方法是否實現
    _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);//模型轉字典後補充處理方法是否實現
    _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);//根據dic的內容轉換爲不一樣類型的模型方法是否實現
    return self;
}
複製代碼
  • 2.1.一、從Class中獲取類信息,並封裝成YYClassInfo對象YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
    + (instancetype)classInfoWithClass:(Class)cls {
        if (!cls) return nil;
        static CFMutableDictionaryRef classCache;//聲明類信息緩存字典
        static CFMutableDictionaryRef metaCache;//聲明元類信息緩存字典
        static dispatch_once_t onceToken;
        static dispatch_semaphore_t lock;//聲明安全鎖
        dispatch_once(&onceToken, ^{
            classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);//類信息緩存d字典實例化
            metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);//元類信息緩存字典實例化
            lock = dispatch_semaphore_create(1);
        });
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        ///根據類名,從緩存獲取類/元類信息
        YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
        if (info && info->_needUpdate) {
            //2.1.1.1 更新類信息
            [info _update];
        }
        dispatch_semaphore_signal(lock);
        if (!info) {
            //2.1.1.2緩存中沒有獲取到類信息,根據cls,初始化類信息
            info = [[YYClassInfo alloc] initWithClass:cls];
            if (info) {
                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
                ///緩存到字典
                CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
                dispatch_semaphore_signal(lock);
            }
        }
        return info;
    }
    複製代碼
    • 2.1.1.1 更新類信息_update方法
      - (void)_update {
          ///清空原有的成員列表,方法,屬性數組
          _ivarInfos = nil;
          _methodInfos = nil;
          _propertyInfos = nil;
      
          Class cls = self.cls;
          //獲取方法列表
          unsigned int methodCount = 0;
          Method *methods = class_copyMethodList(cls, &methodCount);
          if (methods) {
              NSMutableDictionary *methodInfos = [NSMutableDictionary new];
              _methodInfos = methodInfos;
              for (unsigned int i = 0; i < methodCount; i++) {
                  //對Method 作了一層封裝,封裝成了YYClassMethodInfo
                  YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
                  if (info.name) methodInfos[info.name] = info;
              }
              free(methods);
          }
          // 3.獲取屬性列表
          unsigned int propertyCount = 0;
          objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
          if (properties) {
              NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
              _propertyInfos = propertyInfos;
              for (unsigned int i = 0; i < propertyCount; i++) {
                  // 對Property作了一層封裝,封裝成了YYClassPropertyInfo
                  YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
                  if (info.name) propertyInfos[info.name] = info;
              }
              free(properties);
          }
          // 4.獲取示例變量列表
          unsigned int ivarCount = 0;
          Ivar *ivars = class_copyIvarList(cls, &ivarCount);
          if (ivars) {
              NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
              _ivarInfos = ivarInfos;
              for (unsigned int i = 0; i < ivarCount; i++) {
                  // 對成員變量作了一層封裝,封裝成了YYClassIvarInfo
                  YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
                  if (info.name) ivarInfos[info.name] = info;
              }
              free(ivars);
          }
          
          if (!_ivarInfos) _ivarInfos = @{};
          if (!_methodInfos) _methodInfos = @{};
          if (!_propertyInfos) _propertyInfos = @{};
          
          _needUpdate = NO;
      }
      複製代碼
    • 2.1.1.2 根據cls,初始化類信息,YYClassInfo的initWithClass:方法;
      - (instancetype)initWithClass:(Class)cls {
          if (!cls) return nil;
          self = [super init];
          _cls = cls;
          _superCls = class_getSuperclass(cls);
          _isMeta = class_isMetaClass(cls);
          if (!_isMeta) {
              _metaCls = objc_getMetaClass(class_getName(cls));
          }
          _name = NSStringFromClass(cls);
          [self _update];
          
          _superClassInfo = [self.class classInfoWithClass:_superCls];
          return self;
      }
      複製代碼
  • 2.1.二、實例化_YYModelPropertyMeta+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic方法
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
    // support pseudo generic class with protocol name
    if (!generic && propertyInfo.protocols) {
        for (NSString *protocol in propertyInfo.protocols) {
            Class cls = objc_getClass(protocol.UTF8String);
            if (cls) {
                generic = cls;
                break;
            }
        }
    }
    _YYModelPropertyMeta *meta = [self new];
    meta->_name = propertyInfo.name;
    meta->_type = propertyInfo.type;
    meta->_info = propertyInfo;
    meta->_genericCls = generic;
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
        meta->_nsType = YYClassGetNSType(propertyInfo.cls);
    } else {
        meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
    }
    if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
        /*
         It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
         */
        static NSSet *types = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSMutableSet *set = [NSMutableSet new];
            // 32 bit
            [set addObject:@"{CGSize=ff}"];
            [set addObject:@"{CGPoint=ff}"];
            [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
            [set addObject:@"{CGAffineTransform=ffffff}"];
            [set addObject:@"{UIEdgeInsets=ffff}"];
            [set addObject:@"{UIOffset=ff}"];
            // 64 bit
            [set addObject:@"{CGSize=dd}"];
            [set addObject:@"{CGPoint=dd}"];
            [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
            [set addObject:@"{CGAffineTransform=dddddd}"];
            [set addObject:@"{UIEdgeInsets=dddd}"];
            [set addObject:@"{UIOffset=dd}"];
            types = set;
        });
        if ([types containsObject:propertyInfo.typeEncoding]) {
            meta->_isStructAvailableForKeyedArchiver = YES;
        }
    }
    meta->_cls = propertyInfo.cls;
    if (generic) {
        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
    } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    }
    if (propertyInfo.getter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    if (propertyInfo.setter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
            meta->_setter = propertyInfo.setter;
        }
    }
    if (meta->_getter && meta->_setter) {
        /*
         KVC invalid type: KVC失效類型:長整型  雙精度  指針(SEL/CF對象)
         long double    
         pointer (such as SEL/CoreFoundation object)
         */
        switch (meta->_type & YYEncodingTypeMask) {
            case YYEncodingTypeBool:
            case YYEncodingTypeInt8:
            case YYEncodingTypeUInt8:
            case YYEncodingTypeInt16:
            case YYEncodingTypeUInt16:
            case YYEncodingTypeInt32:
            case YYEncodingTypeUInt32:
            case YYEncodingTypeInt64:
            case YYEncodingTypeUInt64:
            case YYEncodingTypeFloat:
            case YYEncodingTypeDouble:
            case YYEncodingTypeObject:
            case YYEncodingTypeClass:
            case YYEncodingTypeBlock:
            case YYEncodingTypeStruct:
            case YYEncodingTypeUnion: {
                meta->_isKVCCompatible = YES;
            } break;
            default: break;
        }
    }
    return meta;
}
複製代碼

三、真正來實現JSON模型轉換功能的是yy_modelSetWithDictionary:,實現以下:

- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
        if (!dic || dic == (id)kCFNull) return NO;
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        // 獲取類信息,這裏若是使用的是原來的類,則實際上是從緩存中取出來的,由於在前面已經調用過metaWithClass方法了。若是是設置了轉換的類,則可能會再從新完整執行一次metaWithClass。
        _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
        if (modelMeta->_keyMappedCount == 0) return NO;
        //解析前是否須要更改字典
        if (modelMeta->_hasCustomWillTransformFromDictionary) {
            dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
            if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        }
        // 2.自定義的一個context 結構體,把model 的信息、model 對象指針、以及參數字典賦值上
        ModelSetContext context = {0};
        context.modelMeta = (__bridge void *)(modelMeta);
        context.model = (__bridge void *)(self);
        context.dictionary = (__bridge void *)(dic);
        
        if (modelMeta-> >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
            CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
            if (modelMeta->_keyPathPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
            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);
        }
        // 最後,若是有一些特殊的屬性,須要本身轉換賦值的話,再處理一下
        if (modelMeta->_hasCustomTransformFromDictionary) {
            return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
        }
        return YES;
    }
複製代碼
3.一、CFDictionaryApplyFunctionCFArrayApplyFunction整體概述

上面真正起到遍歷賦值的方法是CFDictionaryApplyFunctionCFArrayApplyFunction,經過這兩個CoreFoundation的方法來遍歷傳入的字典來給模型類賦值。這兩個方法和OC自帶的遍歷方法相比,會帶來很多性能上的提高,缺點就是寫起來至關麻煩。這兩個方法都有一個回調函數(ModelSetWithDictionaryFunctionModelSetWithPropertyMetaArrayFunction),在遍歷字典時,將字典的每個value賦值給對應的模型類中的對應屬性。

  • CFDictionaryApplyFunctionCFArrayApplyFunction在官方文檔裏有解釋:
// Calls a function once for each key-value pair in a dictionary.
// 對於字典裏的每個鍵值對,都會調用一次applier 方法
void CFDictionaryApplyFunction(CFDictionaryRef theDict, CFDictionaryApplierFunction applier, void *context);
複製代碼
//Calls a function once for each element in range in an array。
// 對於數組中指定range返回的每個元素調用一次applier
void CFArrayApplyFunction(CFArrayRef theArray, CFRange range, CFArrayApplierFunction applier, void *context);
複製代碼

YYModel中對兩個回調函數的實現以下:

3.二、CFDictionaryApplyFunction的回調函數ModelSetWithDictionaryFunction的實現:
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    // 1.從上下文中取到model 的信息
    __unsafe_unretained _YYModelMeta meta = (__bridge _YYModelMeta )(context->modelMeta);
    // 2.從轉換字典中取到屬性對象
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    __unsafe_unretained id model = (__bridge id)(context->model);
    // 3.以防有多個相同key 的不一樣值
     while (propertyMeta) {
        if (propertyMeta->_setter) {
        // 爲model 的該屬性賦值。
        ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    };
}
複製代碼

ModelSetValueForProperty方法中會根據屬性的類型調用objc_msgSend來賦相應類型的值。例如字符串類型的賦值:

if (meta->_nsType == YYEncodingTypeNSString) {
    ((void ()(id, SEL, id))(void ) objc_msgSend)((id)model, meta->_setter, value);
}
複製代碼
3.三、CFArrayApplyFunction的回調函數ModelSetWithPropertyMetaArrayFunction 與字典的處理方式相似,只不過applier 中的參數直接就是屬性對象罷了。
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    // 獲取字典參數
    __unsafe_unretained NSDictionary dictionary = (__bridge NSDictionary )(context->dictionary);
    // 這裏只是強轉一下類型而已
    __unsafe_unretained _YYModelPropertyMeta propertyMeta = (__bridge _YYModelPropertyMeta )(_propertyMeta);
    if (!propertyMeta->_setter) return;
    id value = nil;
    // 這裏由於value 的值,對象的可能有keyPath,也有直接的key。因此用不一樣的方式來取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) {
        // 獲取model 的指針
        __unsafe_unretained id model = (__bridge id)(context->model);
        // 這裏就是爲model 賦值啦
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}
複製代碼

因爲本人水平有限,文中若有不足之處,望大神指出。
若是你看完後以爲對你有所幫助,勿忘點贊+關注
附本文的Demo,贈人玫瑰,手有餘香。

相關文章
相關標籤/搜索