經典iOS第三方庫源碼分析 - YYModel


YYModel介紹

YYModel是一個針對iOS/OSX平台的高性能的Model解析庫,是屬於YYKit的一個組件,創建是ibireme

其實在YYModel出現之前,已經有非常多的Model解析庫,例如JSONModelMantleMJExtension

YYModel從易用性和性能方面均達到了最高水平。

性能

 
Model解析庫對比

特性

  • High performance: The conversion performance is close to handwriting code.
  • Automatic type conversion: The object types can be automatically converted.
  • Type Safe: All data types will be verified to ensure type-safe during the conversion process.
  • Non-intrusive: There is no need to make the model class inherit from other base class.
  • Lightwight: This library contains only 5 files.
  • Docs and unit testing: 100% docs coverage, 99.6% code coverage.

YYModel使用

簡單Model和JSON轉換

// JSON: { "uid":123456, "name":"Harry", "created":"1965-07-31T00:00:00+0000" } // Model: @interface User : NSObject @property UInt64 uid; @property NSString *name; @property NSDate *created; @end @implementation User @end // Convert json to model: User *user = [User yy_modelWithJSON:json]; // Convert model to json: NSDictionary *json = [user yy_modelToJSONObject]; 

內嵌Model

// JSON { "author":{ "name":"J.K.Rowling", "birthday":"1965-07-31T00:00:00+0000" }, "name":"Harry Potter", "pages":256 } // Model: (no need to do anything) @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; @end @implementation Book @end 

集合類型 - Array、Set

@class Shadow, Border, Attachment; @interface Attributes @property NSString *name; @property NSArray *shadows; //Array<Shadow> @property NSSet *borders; //Set<Border> @property NSMutableDictionary *attachments; //Dict<NSString,Attachment> @end @implementation Attributes + (NSDictionary *)modelContainerPropertyGenericClass { // value should be Class or Class name. return @{@"shadows" : [Shadow class], @"borders" : Border.class, @"attachments" : @"Attachment" }; } @end 

YYModel代碼結構

YYModel整個項目非常簡潔,只有5個文件。

文件 描述
NSObject+YYModel YYModel對於NSObject的擴展
YYClassInfo 類信息
YYModel.h YYModel的頭文件

詳細分析

以一個例子來分析,外部是Book對象,內部有一個Author對象。

    NSString *json = @"{ \ \"author\":{ \ \"name\":\"J.K.Rowling\", \ \"birthday\":\"1965-07-31T00:00:00+0000\" \ }, \ \"name\":\"Harry Potter\", \ \"pages\":256 \ }"; Book *book = [Book yy_modelWithJSON:json]; 

yy_modelWithJSON

入口從[NSObject yy_modelWithJSON]進入

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

_yy_dictionaryWithJSON:將JSON的數據(String或者NSData)轉換成NSDictionary,主要使用系統方法[NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];

yy_modelWithDictionary

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { ... Class cls = [self class]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } NSObject *one = [cls new]; if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; } 

modelCustomClassForDictionary - Model類可以重載這個方法,將JSON轉換成另外一個Model類
后續處理都放在了yy_modelSetWithDictionary這個方法

yy_modelSetWithDictionary

首先根據Class信息構造出_YYModelMeta

_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; 

_YYModelMeta中包含如下屬性:

  • YYClassInfo *_classInfo:類信息,例如class、superclass、ivarInfo、methodInfos、propertyInfos
  • NSDictionary *_mapper:屬性key和對應的_YYModelPropertyMeta
{
    author = "<_YYModelPropertyMeta: 0x6080000f5c00>"; name = "<_YYModelPropertyMeta: 0x6080000f5b00>"; pages = "<_YYModelPropertyMeta: 0x6080000f5b80>"; } 

看下Name里面對應的_YYModelPropertyMeta的內容:

 

 
_YYModelPropertyMeta
* _name: 對應的是property的名字
* _nsType:對應property的類型
* _getter:getter方法 * _setter:setter方法 
  • NSArray *_allPropertyMetas:所有的_YYModelPropertyMeta
  • NSArray *_keyPathPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to a key path
  • NSArray *_multiKeysPropertyMetas:Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.

數據填充

    if (modelMeta->_keyMappedCount >= 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); } 

CFDictionaryApplyFunction/CFArrayApplyFunction:針對NSDictionary和NSArray的每一個值,執行一個方法。Context作為方法中一個參數,帶入了Model的信息。

Context數據結構如下:

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

ModelSetWithDictionaryFunction

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); while (propertyMeta) { if (propertyMeta->_setter) { ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; } 

這個方法是將Dictionary的數據填充到Model的核心過程。
通過Context獲取meta(Model的類信息),通過meta獲取當前Key的propertyMeta(屬性信息),遞歸調用ModealSetValueForProperty填充model里面對應Key的Property。

ModelSetValueForProperty

這個方法會將數據填充到Model對應的Property中。

對於普通數據類型的數據填充,大體如下:

switch (meta->_nsType) { case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { if (meta->_nsType == YYEncodingTypeNSString) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); } } 

對於內嵌的對象屬性,處理如下:
Value通常是一個NSDicationary,如果有getter方法,獲取這個property的對象,如果為nill則創建一個實例,再通過[one yy_modelSetWithDictionary:value],填充這個property對象。

            case YYEncodingTypeObject: { ... else if ([value isKindOfClass:[NSDictionary class]]) { NSObject *one = nil; if (meta->_getter) { one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); } if (one) { [one yy_modelSetWithDictionary:value]; } else { Class cls = meta->_cls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:value]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } one = [cls new]; [one yy_modelSetWithDictionary:value]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); } } } break; 

最佳實踐

force_inline

在YYModel實現中大量使用force_inline關鍵詞來修飾方法,inline的作用可以參考Wikipedia: Inline Function。Inline Function會在編譯階段將方法實現直接拷貝到調用處,減少方法參數傳遞和查找,可以提高運行效率。

YYMode的使用方法如下:

#define force_inline __inline__ __attribute__((always_inline)) static force_inline YYEncodingNSType YYClassGetNSType(Class cls) { ... } 

一次性初始化

對於一次性初始化的代碼盡量放在dispatch_once block中,保證只會初始化一次。

static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); 

Lock

通過Lock來保證多線程執行的一致性

static dispatch_semaphore_t lock; dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // do something dispatch_semaphore_signal(lock); 

緩存的實現

通過CFDictionaryCreateMutable實現了一個簡易的文件緩存,注意在讀取和寫入緩存的時候都使用了Lock來保證多線程一致性。

+ (instancetype)metaWithClass:(Class)cls { if (!cls) return nil; 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); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (!meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; } 

總結

YYModel是一個非常簡潔、高性能的Model解析庫,作者使用了大量的runtime方式解析class內部信息,使用了inline、緩存、Lock等方式提高了性能和安全性。
多讀經典的開源庫,理解作者的實現方式,對於提高iOS設計和編程能力有很大的幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM