YYModel底層解析- Runtime

這段時間一直在忙新的需求,沒有時間來整理代碼,發表本身技術博客,今天咱們來看一下YYModel的底層解析以及如何使用,但願對你們有所幫助!數組

 

一 概述

歸納

YYModel是一個輕量級的JSON模型轉換庫,它的思路很是清晰代碼風格也很好,因此仍是建議你們看一下底層實現的邏輯,也能夠從源碼加深對Runtime的理解。緩存

簡介

下面是YYModel第三方庫的一些代碼結構。安全

YYModel的總共文件只有5個文件數據結構

除掉YYModel.h以外,只剩下了YYClassInfo和NSObject+YYModel兩個模塊啦!app

  • YYClassInfo功能主要是將Runtime層級中的一些結構體封裝到NSObject中調用;

  • NSObject+YYModel功能是提供調用的接口以及實現具體的模型轉換邏輯。

前面已經講到YYClassInfo主要功能是將Runtime層級的結構體封裝到NSObject層級以便調用。下面是YYClassInfo與Runtime層級對比:框架

 

2、詳細

1.YYClassIvarInfo

YYClassIvarInfo && objc_ivar

下面是YYClassIvarInfoide

/**
 Instance variable information.
 */
@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

/**
 Creates and returns an ivar info object.
 
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (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
}

注:平常開發中,NSString類型的屬性會用copy修飾,看上面YYClassIvarInfo中typeEncoding和name是用strong修飾。這是由於其內部先是經過Runtime方法拿到const char * 以後經過 stringWithUTF8String 方法以後轉爲 NSString 的。因此 NSString 這類屬性在肯定其不會在初始化以後出現被修改的狀況下,使用 strong來修飾 作一次單純的強引用在性能上是比 copy 要高的。性能

 

YYClassMethodInfo && objc_method

下面是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

YYClassMethodInfo則是對Rutime裏面的objc_method的封裝,緊接着咱們看Runtime的objc_method結構體

struct objc_method {
    SEL _Nonnull method_name OBJC2_UNAVAILABLE; // 方法名稱
    char * _Nullable method_types OBJC2_UNAVAILABLE; // 方法類型
    IMP _Nonnull method_imp OBJC2_UNAVAILABLE; // 方法實現(函數指針)
}

 

YYClassPropertyInfo && property_t

YYClassPropertyInfo是對Runtime中property_t的封裝

@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; @end</nsstring *>

而後來看一下Runtime的property_t結構體

struct property_t {
    const char *name; // 名稱
    const char *attributes; // 修飾
};

 

YYClassInfo && objc_class

YYClassInfo封裝了Runtime的objc_class,下面看一下YYClassInfo

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

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;

註解:下面是Runtime關於class的知識

下面是對應的講解。

 

YYClassInfo 的初始化

+ (instancetype)classInfoWithClass:(Class)cls {
    // 判空入參
    if (!cls) return nil;
     
    // 單例緩存 classCache 與 metaCache,對應緩存類和元類
    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);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        // 這裏把 dispatch_semaphore 當作鎖來使用(當信號量只有 1 時)
        lock = dispatch_semaphore_create(1);
    });
     
    // 初始化以前,首先會根據當前 YYClassInfo 是否爲元類去對應的單例緩存中查找
    // 這裏使用了上面的 dispatch_semaphore 加鎖,保證單例緩存的線程安全 
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    // 若是找到了,且找到的信息須要更新的話則執行更新操做
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
     
    // 若是沒找到,纔會去老實初始化
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        if (info) { // 初始化成功
            // 線程安全
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            // 根據初始化信息選擇向對應的類/元類緩存注入信息,key = cls,value = info
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
     
    return info;
}

下面總結一下初始化主要步驟:

  1. 首先建立單例緩存,類緩存和元類緩存;
  2. 使用dispatch_semaphore 保證緩存線程安全;
  3. 初始化操做以前首先緩存中查找是否已經向緩存中註冊過的當前要初始化的YYClassInfo;
  4. 若是查找緩存對象,須要判斷對象是否須要更新以及其餘相關操做;
  5. 若是沒有找到緩存對象,就開始初始化;
  6. 初始化成功以後,向緩存中註冊YYClassInfo實例。

 

2.NSObject+YYModel

 

NSObject+YYModel在YYModel主要任務是利用YYClassInfo層級封裝的類來執行JSON模型之間的轉換邏輯。下面是NSObject+YYModel講述的主要內容:

  • 類型編碼的解析
  • 數據結構的定義
  • 遞歸模型的轉換
  • 接口相關的代碼

下面將部分講解

數據結構的定義

NSObject+YYModel從新定義了兩個類,來使用 YYClassInfo 中的封裝。

_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;
     
    // 忽略
    ...
}
@end

 

3、使用

1.簡單Model與JSON相互轉換

#import <Foundation/Foundation.h>
//"time":"2018-07-04 12:13:52",
//"ftime":"2018-07-04 12:13:52",
//"context":"快件已簽收 簽收人: 他人代收 感謝使用圓通速遞,期待再次爲您服
@interface IOALogisticsDetailModel : NSObject
@property(nonatomic,copy)NSString   *time;
@property(nonatomic,copy)NSString   *ftime;
@property(nonatomic,copy)NSString   *context;

@end

#import "IOALogisticsDetailModel.h"

@implementation IOALogisticsDetailModel

@end

下面是運用

- (NSArray <IOALogisticsDetailModel *>*)setupOrderWithArray:(NSArray <NSDictionary *>*)array{
    NSMutableArray <IOALogisticsDetailModel *>*modelArray = [NSMutableArray arrayWithCapacity:array.count];
    for(NSDictionary *dic in array){
 IOALogisticsDetailModel *model = [IOALogisticsDetailModel yy_modelWithDictionary:dic];
        if (!model) continue;
        [modelArray addObject:model];
    }
    return modelArray;
}

紅色部分就是應用。

若是須要Model轉爲JSON以下

#import <Foundation/Foundation.h>

@interface IOAOrderAftersaleRequestModel : NSObject
@property(nonatomic,copy)NSString *order_sn;
@property(nonatomic,copy)NSString *rec_id;
@property(nonatomic,copy)NSString  *is_type;
@property(nonatomic,assign)NSInteger refund_count;
@property(nonatomic,copy)NSString *content;
@property(nonatomic,copy)NSString *return_attachs;
@property(nonatomic,copy)NSString *shop_id;

@property(nonatomic,copy)NSString *reason;
@property(nonatomic,assign)float total;

@end


#import "IOAOrderAftersaleRequestModel.h"

@implementation IOAOrderAftersaleRequestModel

@end

下面是運用

//提交退貨商品
@interface IOAOrderAftersaleRequest:IOARequest
@property (nonatomic,strong)IOAOrderAftersaleRequestModel *requestModel;

@end


//提交退貨商品
@implementation IOAOrderAftersaleRequest
- (id)requestArgument{
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Order.SetOrderAftersaleGoodsrefundsList"];
 NSDictionary *temDic = [self.requestModel yy_modelToJSONObject];     [dic addEntriesFromDictionary:temDic];
    return dic;
}

 

2.Model屬性名與JSON中key不一樣

// JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}
 
// Model:
@interface Book : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation Book
//返回一個 Dict,將 Model 屬性名對映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"name" : @"n",
             @"page" : @"p",
             @"desc" : @"ext.desc",
             @"bookID" : @[@"id",@"ID",@"book_id"]};
}

 

3.Model包含Model

// JSON
{
    "author":{
        "name":"J.K.Rowling",
        "birthday":"1965-07-31T00:00:00+0000"
    },
    "name":"Harry Potter",
    "pages":256
}
 
// Model: 什麼都不用作,轉換會自動完成
@interface Author : NSObject
@property NSString *name;
@property NSDate *birthday;
@end
@implementation Author
@end
 
@interface Book : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author; //Book 包含 Author 屬性
@end
@implementation Book
@end

下面在咱們項目中的使用

#import <Foundation/Foundation.h>
#import "IOAOrder.h"

@interface IOAOrderGroup : NSObject

@property(nonatomic,copy)NSString *order_id;
@property(nonatomic,copy)NSString *parent_sn;
@property(nonatomic,copy)NSString *order_sn;
@property(nonatomic,copy)NSString *order_status;
@property(nonatomic,copy)NSString *refund_status;
@property(nonatomic,copy)NSString *return_status;
@property(nonatomic,copy)NSString *pay_status;
@property(nonatomic,copy)NSString *total_amount;
@property(nonatomic,copy)NSString *company_name;
@property(nonatomic,copy)NSString *company_logo;
@property(nonatomic,copy)NSString *shop_id;
@property(nonatomic,copy)NSString *stroe_id;
@property(nonatomic,copy)NSString *order_amount;
@property(nonatomic,assign)int store_id;
@property (nonatomic,strong)NSArray<IOAOrder *> *goods_list;

@end



#import "IOAOrderGroup.h"
#import <YYModel/YYModel.h>

@implementation IOAOrderGroup

+ (NSDictionary *)modelContainerPropertyGenericClass{
    return @{@"goods_list":[IOAOrder class]};
}


@end



#import <Foundation/Foundation.h>

@interface IOAOrder : NSObject
@property(nonatomic,copy)NSString *rec_id;
@property(nonatomic,copy)NSString *order_id;
@property(nonatomic,copy)NSString *brand_name;
@property(nonatomic,copy)NSString *goods_id;
@property(nonatomic,copy)NSString *goods_name;
@property(nonatomic,copy)NSString *goods_sn;
@property(nonatomic,copy)NSString *goods_num;
@property(nonatomic,copy)NSString *market_price;
@property(nonatomic,copy)NSString *goods_price;
@property(nonatomic,copy)NSString *cost_price;
@property(nonatomic,copy)NSString *member_goods_price;
@property(nonatomic,copy)NSString *total_price;
@property(nonatomic,copy)NSString *give_integral;
@property(nonatomic,copy)NSString *spec_key;
@property(nonatomic,copy)NSString *unit;
@property(nonatomic,copy)NSString *spec_key_name;
@property(nonatomic,copy)NSString *bar_code;
@property(nonatomic,copy)NSString *is_comment;
@property(nonatomic,copy)NSString *prom_type;
@property(nonatomic,copy)NSString *prom_id;
@property(nonatomic,copy)NSString *is_send;
@property(nonatomic,copy)NSString *delivery_id;
@property(nonatomic,copy)NSString *add_time;
@property(nonatomic,copy)NSString *update_time;
@property(nonatomic,copy)NSString *image_url;

@property(nonatomic,assign)BOOL selected;

@end



#import "IOAOrder.h"

@implementation IOAOrder

@end
View Code

 

4.白名單黑名單

@interface User
@property NSString *name;
@property NSUInteger age;
@end
 
@implementation Attributes
// 若是實現了該方法,則處理過程當中會忽略該列表內的全部屬性
+ (NSArray *)modelPropertyBlacklist {
    return @[@"test1", @"test2"];
}
// 若是實現了該方法,則處理過程當中不會處理該列表外的屬性。
+ (NSArray *)modelPropertyWhitelist {
    return @[@"name"];
}
@end

 

 

 YYModel的核心是經過runtime獲取結構體中得Ivars的值,將此值定義爲key,而後給key賦value值,因此咱們須要本身遍歷容器(NSArray,NSSet,NSDictionary),獲取每個值,而後KVC。

相關文章
相關標籤/搜索