YYModel 學習

如何集成?

  • 支持CocoaPods,在 Podfile 中添加 pod 'YYModel'

  • 支持Carthage,在 Cartfile 中添加 github "ibireme/YYModel"

  • 在需要使用的地方只需要包含“YYModel.h”這一個頭文件就可以了。這個頭文件沒有具體內容,只是加了一層包含關系,方便使用。

  • 一些可用的API,都在文件“NSObject+YYModel.h”中,注釋還是比較詳細的。是NSObject的類別,所以對于自定義的Model可以直接使用。這個和JSONModel需要一個固定基類相比在用法上要簡潔一點。
  • 定義也在文件“NSObject+YYModel.h”中,卻是對NSArray和NSDictionary的類別。提供了方便方法,對于集合中的元素進行JSON -》Model的轉化,將JSON對象的集合轉變為Model的集合。

基本用法

JSON -》Model

  • + (nullable instancetype)yy_modelWithJSON:(id)json;
  • 這是一個類方法,直接使用,返回一個類的實例instancetype,相當于初始化函數[[xxx alloc] init];
  • 輸入參數json的類型是id,動態類型;期望是一個JSON對象,類型可以是NSDictionary, NSString or NSData. 從網絡上傳過來什么可以直接用,很方便。
  • 不需要調用系統函數將JSON轉換為Dictionary,YYModel里面已經做了這一步了。
  • 返回的實例instancetype有可能為nil

Model -》JSON

  • - (nullable id)yy_modelToJSONObject;
  • 這是一個實例方法。實例已經存在,將實例轉換為JSON對象,然后網絡傳輸,符合使用場景。
  • 返回值是id,動態類型;結果是一個JSON對象,類型可以是NSDictionary or NSArray,符合JSON的定義(字典或者數組)。JSON 數據格式
  • 返回的JSON對象有可能為nil

實際的例子

YYModel

JSON對象

// JSON:
{
    "uid":123456,
    "name":"Harry",
    "created":"1965-07-31T00:00:00+0000"
}

Model定義

// Model:
@interface User : NSObject
@property UInt64 uid;
@property NSString *name;
@property NSDate *created;
@end

@implementation User
@end

JSON -》Model

// 將 JSON (NSData,NSString,NSDictionary) 轉換為 Model:
User *user = [User yy_modelWithJSON:json];

Model -》JSON

// 將 Model 轉換為 JSON 對象:
NSDictionary *json = [user yy_modelToJSONObject];

NSArray JSON -》Model

對于JSON對象數組的情況,[{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}],對數組中的成員進行轉換,得到一個Model的數組[user1, user2]

/**
 Provide some data-model method for NSArray.
 */
@interface NSArray (YYModel)

/**
 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;

@end
  • 這個方法對于返回JSON對象數組的網絡請求比較方便,這是數組的類別,可以用數組直接調用,返回值就是一個Model的數組,省去了數組遍歷的步驟
  • 數組的Model-》JSON的過程沒有提供方便方法

NSDictionary JSON -》Model

對于JSON對象字典的情況,{"user1":{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, "user2": {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}}, 對字典中的值進行轉換,key保持不變,得到一個值為Model的新字典{"user1" : user1, "user2" : user2}

/**
 Provide some data-model method for NSDictionary.
 */
@interface NSDictionary (YYModel)

/**
 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;
@end
  • 對于字典,一般會定義一個與之對應的Model進行互相轉換。這種將Model定義與字典的值進行對應的使用場景有點特殊。這種場景容易跟普通使用方法混淆,不推薦用。
  • 字典的Model-》JSON的過程沒有提供方便方法

特殊情況處理

通過一個協議,方便讓使用者來指定一些特殊情況的處理方法。這些方法都是可選的。

/**
 If the default model transform does not fit to your model class, implement one or
 more method in this protocol to change the default key-value transform process.
 There's no need to add '<YYModel>' to your class header.
 */
@protocol YYModel <NSObject>
@optional
@end

不需要加 <YYModel>是因為實現者(代理)是Model自己。這是特殊的用法。

1. Model 屬性名和 JSON 中的 Key 不相同

協議方法:

+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;

JSON對象:

// JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext" : {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}

Model定義:

// 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"]};
}
@end

2. 容器類屬性(NSArray/NSSet/NSDictionary)

協議方法:

+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;

Model定義:

@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
// 返回容器類中的所需要存放的數據類型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass {
    return @{@"shadows" : [Shadow class],
             @"borders" : Border.class,
             @"attachments" : @"Attachment" };
}
@end

其他的特性比如黑白名單,轉換后的校驗,自定義字典key對應的類等功能不是很常用。
Model中包含Model的方式是支持的,不需要特殊指定。對頂級類NSObeject加類別,優勢就體現出來了。

對比測試

  • 在下載的包中有一個對比測試程序,ModelBenchmark.xcodeproj,重點聚焦在“性能”和“容錯”兩個方面。通過數據對比,還做了excel的圖表,一目了然,視覺震撼比較大。

  • 同時還寫了測評博客,更方便傳播。相對于其他方案,“性能”提升非常明顯,在博客里也解釋了這點,讓人比較信服。iOS JSON 模型轉換庫評測

  • GitHub user的例子是簡單使用。weibo Model是比較復雜的例子,涉及key map,容器類處理等核心內容。

  • 序列化,要實現NSCoding的協議函數。還有深拷貝,判斷是否相等,hash值等都是在Model定義時有可能涉及的內容。簡單但繁瑣,通過例子也給出了簡潔的使用方法。

#define YYModelSynthCoderAndHash \
- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } \
- (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; } \
- (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } \
- (NSUInteger)hash { return [self yy_modelHash]; } \
- (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; }

在實際使用中,用上面這個宏,不鼓勵也不反對。
根據實際情況,實現上面的函數。使用YYModel后,只要一句話調用就好了,帶來了比較大的便利。

  • 在測試“容錯”那部分,logError是一個變量,是一個block,相當于一個匿名函數。在這個場景中,比有名函數調用要靈活很多。在log中,通過?等字符很形象啊。

  • 對于崩潰的情況,使用了try catch結構。通過對比,可以看出FastEasyMappingJSONModel“容錯”性能比較差,如果后臺數據返回錯誤,很容易崩潰。YYModel的“容錯”性能是最好的,并且“性能”有5~10倍的提升,這兩個庫是可以考慮替換掉。

  • 在“性能”測試中,用了CACurrentMediaTime()這個函數來統計時間,將for循環放在一個@autoreleasepool中。相當于[[NSDate data] timeIntervalSinceReferenceDate];``, 在QuartzCore`框架中。
    NSDate 、CFAbsoluteTimeGetCurrent、CACurrentMediaTime 的區別

begin = CACurrentMediaTime();
@autoreleasepool {
    for (int i = 0; i < count; i++) {
        NSDictionary *json = [user yy_modelToJSONObject];
        [holder addObject:json];
    }
}
end = CACurrentMediaTime();
  • 在一個大函數中,通過{}進行分塊。有時候switch語句編譯不過,將case下面的內容包起來就好了。基本上用到的場合不多。

數據結構

iOS開發-Runtime詳解
Objective-C Runtime 運行時之三:方法與消息
Objective-C中的一些特殊的數據類型 id、nil、Nil、SEL

id

在文件objc/objc.h

/// A pointer to an instance of a class.
typedef struct objc_object *id;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

Class

在文件objc/objc.h

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

在文件objc/runtime.h

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統運行版本進行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本,但我們仍能從中獲取一些有用信息。
OC之OBJC2_UNAVAILABLE

SEL

在文件objc/objc.h

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
  • 結構體objc_selector的定義找不到
  • 可以將SEL理解為方法名的hash值,可以加快方法的查找速度
  • C中函數名是函數實現的地址,而SEL只跟函數名有關,不涉及函數實現的地址。將函數名和函數實現分離,可以實現函數交換等功能。
  • SEL只跟方法名有關,跟參數無關,沒有C++中的函數重載功能
  • Opaque Types

IMP

在文件objc/objc.h

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

Method

在文件objc/runtime.h

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

方法:SEL(函數名)、IMP(函數實現)、method_types(參數)的統一體。

Ivar

在文件objc/runtime.h

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

objc_property_t

在文件objc/runtime.h

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
  • Property沒有找到
  • struct objc_property定義沒有找到

Meta Class

  • 實例對象(instance),類(class),元類(meta class) 三者之間的關系通過isa指針聯系起來
  • 類(class)存實例方法(- 開頭的方法)
  • 元類(meta class)存類方法(+ 開頭的方法)


    class.jpg
  • 每個Class都有一個isa指針指向一個唯一的Meta Class
  • 每一個Meta Class的isa指針都指向最上層的Meta Class
  • 最上層的Meta Class的isa指針指向自己,形成一個回路
  • 每一個Meta Class的super class指針指向它原本Class的 Super Class的Meta Class。但是最上層的Meta Class的 Super Class指向NSObject Class本身
  • 最上層的NSObject Class的super class指向 nil
    深入淺出Cocoa之類與對象
    [Cocoa]深入淺出Cocoa 之動態創建類
    刨根問底Objective-C Runtime

YYClassInfo文件

根據原生的數據結構,進行的類抽象。

YYEncodingType

  • 自定義的類型,是一種NS_OPTIONS
  • 將類型,修飾符等信息整合在一個變量中,效率較高。總共用到了3個字節。由不同的mask來整合。
  • YYEncodingType YYEncodingGetType(const char *typeEncoding); 這個全局函數用來將字符轉化為自定義的類型
  • Type Encoding
  • Declared Properties

YYClassIvarInfo

  • 對應Ivar數據結構
  • 構建方法也是從Ivar作為輸入參數
    - (instancetype)initWithIvar:(Ivar)ivar;
  • 用到的系統API
    const char *ivar_getName(Ivar v);
    const char *ivar_getTypeEncoding(Ivar v);
    ptrdiff_t ivar_getOffset(Ivar v);

ptrdiff_t是C/C++標準庫中定義的一個與機器相關的數據類型。ptrdiff_t類型變量通常用來保存兩個指針減法操作的結果。ptrdiff_t定義在stddef.h(cstddef)這個文件內。ptrdiff_t通常被定義為long int類型。
ptrdiff_t定義在C99標準中。

YYClassMethodInfo

  • 對應Method數據結構
  • - (instancetype)initWithMethod:(Method)method;通過Method創建
  • 用到的系統API
    SEL method_getName(Method m);
    IMP method_getImplementation(Method m);
    const char *method_getTypeEncoding(Method m);
    char *method_copyReturnType(Method m);
    unsigned int method_getNumberOfArguments(Method m);
    char *method_copyArgumentType(Method m, unsigned int index);

YYClassPropertyInfo

  • 對應struct objc_property_t數據結構
  • - (instancetype)initWithProperty:(objc_property_t)property;
  • 用到的系統API
    const char *property_getName(objc_property_t property) ;
    objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount);
    SEL NSSelectorFromString(NSString *aSelectorName);
  • 如果是類,通過函數Class objc_getClass(const char *name);獲取屬性的類型信息
  • getter和setter函數,如果沒有,會生成默認的
if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }

YYClassInfo

  • 對應Class數據結構
  • + (instancetype)classInfoWithClass:(Class)cls;
  • 這里用了兩個靜態的字典來存儲類信息。key是Class,value是YYClassInfo。考慮到類型嵌套,會有一大堆的類型信息需要保存。這里用了Core Fountdation的字典。這兩個字典是單例。
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    lock = dispatch_semaphore_create(1);
});
  • 這里也用到了線程保護,使用的GCD
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);
}
  • 用到的系統API
    Class class_getSuperclass(Class cls) ;
    BOOL class_isMetaClass(Class cls);
    Class objc_getMetaClass(const char *name);
    const char *class_getName(Class cls);
    NSString *NSStringFromClass(Class aClass);
    Method *class_copyMethodList(Class cls, unsigned int *outCount);
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount);

NSObject+YYModel文件

YYEncodingNSType

  • 這是NS_ENUM,簡單枚舉,沒有位操作
  • 對應屬性中的Foundation類型,比如NSString,NSNumber等
  • YYEncodingType是包括基本類型,比如int,double等
  • YYEncodingType中的YYEncodingTypeObject進一步細分,可以得到相應的YYEncodingNSType
  • 相關的靜態全局函數是static force_inline YYEncodingNSType YYClassGetNSType(Class cls);

YYNSDateFromString

  • 這是一個靜態全局函數
  • 功能是將NSString轉化為對應的NSDate
  • 由于日期format的種類很多,這里引入了一個block的數組
  • 將NSString的字符個數作為數據的標號,這種思維比較奇特
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
....
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum

_YYModelPropertyMeta

  • YYClassPropertyInfo進一步信息整合
  • 這里對屬性的類型進行判斷:
    是一個Foundation類型,還是一個C數值類型,或者是一個容器類型等

_YYModelMeta

  • YYClassInfo進一步信息整合`
  • 遍歷類所有的屬性,直到根類
// Create all property metas.
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;
        _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;
  • 處理代理函數modelPropertyBlacklist---黑名單
  • 處理代理函數modelPropertyWhitelist---白名單
  • 處理代理函數modelContainerPropertyGenericClass---容器類型指定,支持class類型和字符串的類名
  • 將類所有的屬性轉換為_YYModelPropertyMeta數組
  • 處理代理函數modelCustomPropertyMapper---屬性和JSON鍵名稱的對應關系。這種對應關系支持.格式的鏈式關系和一對多的數組。保存在相應的_YYModelPropertyMeta成員中。
  • 做標記,判斷用戶是否自定義了以下協議函數:
    modelCustomWillTransformFromDictionary
    modelCustomTransformFromDictionary
    modelCustomTransformToDictionary
    modelCustomClassForDictionary

ModelSetContext

typedef struct {
    void *modelMeta;  ///< _YYModelMeta
    void *model;      ///< id (self)
    void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
  • 這是一個結構體
  • 將類信息(_YYModelMeta),類(model),JSON(NSDictionary)等放在一起。
  • model --- modelMeta --- dictionary;相互轉化的兩種結構通過一個中間過渡數據結構,整合在一起
  • 類型都是void *,是C的指針

函數調用流程

JSON -》Model

  1. 起點:+ (nullable instancetype)yy_modelWithJSON:(id)json;
  2. 將JSON對象轉換為字典:+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json

這里用到了系統的JSON轉字典API:+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

  1. 字典轉模型:+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
  2. 設置模型屬性:- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
  3. 利用函數CFDictionaryApplyFunction調用static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context);
    或者,利用函數CFArrayApplyFunction調用static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context);
  4. 上面兩個函數,都調用全局函數:static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta); 所有的實際工作都在這里

Model -》JSON

  • API函數:- (nullable id)yy_modelToJSONObject;

id的類型是NSDictionary or NSArray,根據實際情況指定

  • 如果需要NSDictionary or NSArray的結果,那么調用另外兩個API:
    - (nullable NSData *)yy_modelToJSONData;
    - (nullable NSString *)yy_modelToJSONString;

都是基于- (nullable id)yy_modelToJSONObject;的結果做格式轉換,這里用到了系統API:+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;

  • 實際的工作在全局函數static id ModelToJSONObjectRecursive(NSObject *model);中完成

幾個知識點

objc_msgSend

//objc_msgSend(self,selector,@"test");
((void(*)(id, SEL, id))objc_msgSend)(self, selector, @"test");```
  • (int) doSomething:(int) x { ... }
  • (void) doSomethingElse {
    int (action)(id, SEL, int) = (int ()(id, SEL, int)) objc_msgSend;
    action(self, @selector(doSomething:), 0);
    }```
  • 這里調用的時候多了一個(void *),多了一步強制轉換,本質上都是為了解決64位硬件上崩潰的的問題
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);```

double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);```

__bridge

ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);

ModelSetContext是一個C的struct

ModelSetValueForProperty

作用:將JSON(根式是Dictionary)的value(類型是id)賦值給Model的屬性
方式:通過setter函數來實現,objc_msgSendSEL參數基本上都是meta->_setter

case1:數字類型

  • 實現過程在全局函數中ModelSetNumberToProperty
  • 細分為布爾型,8位,16位,32位,64位整數,浮點數等等各種具體類型

case2:Foundation類型

  1. Model屬性類型是NSString或者NSMutableString;對value(id)的具體類型做了容錯處理:
    ** NSString:直接設置
    ** NSMutableString:
    轉化為NSString之后,調用mutableCopy
    NSNumber:取屬性stringValue
    ** NSData:**轉化為NSString
    NSURL:取屬性absoluteString
    NSAttributedString:取屬性string

  2. Model屬性類型是NSDecimalNumber;對value(id)的類型是NSDecimalNumberNSNumberNSString的情況做了容錯處理

  3. Model屬性類型是NSData;對value(id)的類型是NSString的情況做了容錯處理

  4. Model屬性類型是NSDate;對value(id)的類型是NSString的情況做了容錯處理

  5. Model屬性類型是NSURL;對value(id)的類型是NSString的情況做了容錯處理

  6. Model屬性類型是NSDictionaryNSSetNSArray等容器類型時,對容器中每個成員調用函數yy_modelSetWithDictionary,一層層深入下去。

case3:其他類型

都對value為nil的情況做了處理

  1. Model屬性類型是id對象類型時,調用函數yy_modelSetWithDictionary,一層層深入下去。

  2. Model屬性類型是Class類型時,如果value是NSString,則調用函數NSClassFromString進行轉化,然后設置。如果是其他類型,則判斷其“元類”class_isMetaClass是否存在。存在,則直接設置

  3. Model屬性類型是SEL類型時,如果value是NSString,則調用函數NSSelectorFromString進行轉化,然后設置。

  4. Model屬性類型是Block類型時,將value強制轉換為void (^)()進行設置。

  5. Model屬性類型是structunionchar[10]等類型時,如果value是NSValue,則調用函數- (void)setValue:(nullable id)value forKey:(NSString *)key;進行設置。

  6. Model屬性類型是void*char*等類型時,如果value是NSValue,則將value強制轉換為NSValue,取屬性pointerValue進行設置。進行設置。

個人意見

  1. 看上去只有兩個文件,但是類有很多,用了很多的內部類。這種方式不是很認同。還是推薦一個類一個源文件的方式。當然,這里的場景是高內聚的一個整體,本來也是把一個類的各子成員(都是struct),還是比較合適的。
  2. 將所有的頭文件都歸總為一個YYMode.h,這種方式是非常好的,推薦使用。
  3. 對于NSArray這種集合提供方便方法;對于JSON對象,采用id類型,支持NSDictionary, NSString, NSData三種類型;在處理的時候,統一為NSDictionary。這種方式,統籌考慮了實現和使用的方便性,只是增加了幾層函數調用,值得推薦。
  4. 協議的定義、使用者、實現者都是同一個(self,Model自己),這里是特殊的使用場景。
    一般情況下應該分3個文件(協議定義,使用者,實現者)或者2個文件(協議的定義放在使用者的文件中)。
  5. 采用協議的設計方式,讓使用者對特殊使用場景做自定義,值得推薦
  6. 將_YYModelMeta(runtime中各種struct對應的類)作為轉換的中間載體,思維很巧妙
  7. if后面只有一個語句,省略了{},這種習慣不是很好。
    if (num) [num class]; // hold the number

參考文章

YYModel
iOS JSON 模型轉換庫評測
JSON 數據格式
Objective-C Runtime Programming Guide

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,768評論 0 9
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 終于把前面的base文件夾簡簡單單的看了一遍,終于可以回到正片上來了,保證不爛尾。 項目天天用yymodel解析數...
    充滿活力的早晨閱讀 1,388評論 1 0
  • Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的...
    有一種再見叫青春閱讀 609評論 0 3
  • Objective-C語言是一門動態語言,他將很多靜態語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態語言...
    tigger丨閱讀 1,431評論 0 8