這段時間一直在忙新的需求,沒有時間來整理代碼,發表本身技術博客,今天咱們來看一下YYModel的底層解析以及如何使用,但願對你們有所幫助!數組
YYModel是一個輕量級的JSON模型轉換庫,它的思路很是清晰代碼風格也很好,因此仍是建議你們看一下底層實現的邏輯,也能夠從源碼加深對Runtime的理解。緩存
下面是YYModel第三方庫的一些代碼結構。安全
YYModel的總共文件只有5個文件數據結構
除掉YYModel.h以外,只剩下了YYClassInfo和NSObject+YYModel兩個模塊啦!app
前面已經講到YYClassInfo主要功能是將Runtime層級的結構體封裝到NSObject層級以便調用。下面是YYClassInfo與Runtime層級對比:框架
下面是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編碼
@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是對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封裝了Runtime的objc_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
// 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的知識
下面是對應的講解。
+ (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; }
下面總結一下初始化主要步驟:
NSObject+YYModel在YYModel主要任務是利用YYClassInfo層級封裝的類來執行JSON模型之間的轉換邏輯。下面是NSObject+YYModel講述的主要內容:
下面將部分講解
NSObject+YYModel從新定義了兩個類,來使用 YYClassInfo 中的封裝。
@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
@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
#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; }
// 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"]}; }
// 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
@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。