YYModel 學(xué)習(xí)

如何集成钓试?

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

  • 支持Carthage滨巴,在 Cartfile 中添加 github "ibireme/YYModel"择懂。

  • 在需要使用的地方只需要包含“YYModel.h”這一個頭文件就可以了霎奢。這個頭文件沒有具體內(nèi)容,只是加了一層包含關(guān)系,方便使用吉嫩。

  • 一些可用的API,都在文件“NSObject+YYModel.h”中嗅定,注釋還是比較詳細(xì)的自娩。是NSObject的類別,所以對于自定義的Model可以直接使用渠退。這個和JSONModel需要一個固定基類相比在用法上要簡潔一點(diǎn)忙迁。
  • 定義也在文件“NSObject+YYModel.h”中,卻是對NSArray和NSDictionary的類別碎乃。提供了方便方法姊扔,對于集合中的元素進(jìn)行JSON -》Model的轉(zhuǎn)化,將JSON對象的集合轉(zhuǎn)變?yōu)镸odel的集合梅誓。

基本用法

JSON -》Model

  • + (nullable instancetype)yy_modelWithJSON:(id)json;
  • 這是一個類方法恰梢,直接使用,返回一個類的實(shí)例instancetype梗掰,相當(dāng)于初始化函數(shù)[[xxx alloc] init];
  • 輸入?yún)?shù)json的類型是id嵌言,動態(tài)類型;期望是一個JSON對象及穗,類型可以是NSDictionary, NSString or NSData. 從網(wǎng)絡(luò)上傳過來什么可以直接用摧茴,很方便。
  • 不需要調(diào)用系統(tǒng)函數(shù)將JSON轉(zhuǎn)換為Dictionary埂陆,YYModel里面已經(jīng)做了這一步了苛白。
  • 返回的實(shí)例instancetype有可能為nil

Model -》JSON

  • - (nullable id)yy_modelToJSONObject;
  • 這是一個實(shí)例方法。實(shí)例已經(jīng)存在焚虱,將實(shí)例轉(zhuǎn)換為JSON對象丸氛,然后網(wǎng)絡(luò)傳輸,符合使用場景著摔。
  • 返回值是id缓窜,動態(tài)類型;結(jié)果是一個JSON對象谍咆,類型可以是NSDictionary or NSArray禾锤,符合JSON的定義(字典或者數(shù)組)。JSON 數(shù)據(jù)格式
  • 返回的JSON對象有可能為nil

實(shí)際的例子

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) 轉(zhuǎn)換為 Model:
User *user = [User yy_modelWithJSON:json];

Model -》JSON

// 將 Model 轉(zhuǎn)換為 JSON 對象:
NSDictionary *json = [user yy_modelToJSONObject];

NSArray JSON -》Model

對于JSON對象數(shù)組的情況摹察,[{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}]恩掷,對數(shù)組中的成員進(jìn)行轉(zhuǎn)換,得到一個Model的數(shù)組[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對象數(shù)組的網(wǎng)絡(luò)請求比較方便供嚎,這是數(shù)組的類別黄娘,可以用數(shù)組直接調(diào)用峭状,返回值就是一個Model的數(shù)組,省去了數(shù)組遍歷的步驟
  • 數(shù)組的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"}}, 對字典中的值進(jìn)行轉(zhuǎn)換优床,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
  • 對于字典誓焦,一般會定義一個與之對應(yīng)的Model進(jìn)行互相轉(zhuǎn)換胆敞。這種將Model定義與字典的值進(jìn)行對應(yīng)的使用場景有點(diǎn)特殊。這種場景容易跟普通使用方法混淆杂伟,不推薦用移层。
  • 字典的Model-》JSON的過程沒有提供方便方法

特殊情況處理

通過一個協(xié)議,方便讓使用者來指定一些特殊情況的處理方法赫粥。這些方法都是可選的观话。

/**
 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>是因為實(shí)現(xiàn)者(代理)是Model自己。這是特殊的用法越平。

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

協(xié)議方法:

+ (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)

協(xié)議方法:

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

其他的特性比如黑白名單帽驯,轉(zhuǎn)換后的校驗,自定義字典key對應(yīng)的類等功能不是很常用书闸。
Model中包含Model的方式是支持的尼变,不需要特殊指定。對頂級類NSObeject加類別浆劲,優(yōu)勢就體現(xiàn)出來了嫌术。

對比測試

  • 在下載的包中有一個對比測試程序,ModelBenchmark.xcodeproj牌借,重點(diǎn)聚焦在“性能”和“容錯”兩個方面度气。通過數(shù)據(jù)對比,還做了excel的圖表膨报,一目了然磷籍,視覺震撼比較大。

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

  • GitHub user的例子是簡單使用周循。weibo Model是比較復(fù)雜的例子强法,涉及key map万俗,容器類處理等核心內(nèi)容。

  • 序列化饮怯,要實(shí)現(xiàn)NSCoding的協(xié)議函數(shù)闰歪。還有深拷貝,判斷是否相等硕淑,hash值等都是在Model定義時有可能涉及的內(nèi)容课竣。簡單但繁瑣嘉赎,通過例子也給出了簡潔的使用方法置媳。

#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]; }

在實(shí)際使用中,用上面這個宏公条,不鼓勵也不反對拇囊。
根據(jù)實(shí)際情況,實(shí)現(xiàn)上面的函數(shù)靶橱。使用YYModel后寥袭,只要一句話調(diào)用就好了,帶來了比較大的便利关霸。

  • 在測試“容錯”那部分传黄,logError是一個變量,是一個block队寇,相當(dāng)于一個匿名函數(shù)膘掰。在這個場景中,比有名函數(shù)調(diào)用要靈活很多佳遣。在log中识埋,通過?等字符很形象啊。

  • 對于崩潰的情況零渐,使用了try catch結(jié)構(gòu)窒舟。通過對比,可以看出FastEasyMappingJSONModel“容錯”性能比較差诵盼,如果后臺數(shù)據(jù)返回錯誤惠豺,很容易崩潰。YYModel的“容錯”性能是最好的风宁,并且“性能”有5~10倍的提升耕腾,這兩個庫是可以考慮替換掉。

  • 在“性能”測試中杀糯,用了CACurrentMediaTime()這個函數(shù)來統(tǒng)計時間扫俺,將for循環(huán)放在一個@autoreleasepool中。相當(dāng)于[[NSDate data] timeIntervalSinceReferenceDate];``, 在QuartzCore`框架中固翰。
    NSDate 狼纬、CFAbsoluteTimeGetCurrent羹呵、CACurrentMediaTime 的區(qū)別

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

數(shù)據(jù)結(jié)構(gòu)

iOS開發(fā)-Runtime詳解
Objective-C Runtime 運(yùn)行時之三:方法與消息
Objective-C中的一些特殊的數(shù)據(jù)類型 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系統(tǒng)運(yùn)行版本進(jìn)行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本臼勉,但我們?nèi)阅軓闹蝎@取一些有用信息邻吭。
OC之OBJC2_UNAVAILABLE

SEL

在文件objc/objc.h

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
  • 結(jié)構(gòu)體objc_selector的定義找不到
  • 可以將SEL理解為方法名的hash值,可以加快方法的查找速度
  • C中函數(shù)名是函數(shù)實(shí)現(xiàn)的地址宴霸,而SEL只跟函數(shù)名有關(guān)囱晴,不涉及函數(shù)實(shí)現(xiàn)的地址。將函數(shù)名和函數(shù)實(shí)現(xiàn)分離瓢谢,可以實(shí)現(xiàn)函數(shù)交換等功能畸写。
  • SEL只跟方法名有關(guān),跟參數(shù)無關(guān)氓扛,沒有C++中的函數(shù)重載功能
  • 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(函數(shù)名)枯芬、IMP(函數(shù)實(shí)現(xiàn))、method_types(參數(shù))的統(tǒng)一體幢尚。

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

  • 實(shí)例對象(instance)破停,類(class),元類(meta class) 三者之間的關(guān)系通過isa指針聯(lián)系起來
  • 類(class)存實(shí)例方法(- 開頭的方法)
  • 元類(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 之動態(tài)創(chuàng)建類
    刨根問底Objective-C Runtime

YYClassInfo文件

根據(jù)原生的數(shù)據(jù)結(jié)構(gòu),進(jìn)行的類抽象理茎。

YYEncodingType

  • 自定義的類型黑界,是一種NS_OPTIONS
  • 將類型,修飾符等信息整合在一個變量中皂林,效率較高朗鸠。總共用到了3個字節(jié)础倍。由不同的mask來整合烛占。
  • YYEncodingType YYEncodingGetType(const char *typeEncoding); 這個全局函數(shù)用來將字符轉(zhuǎn)化為自定義的類型
  • Type Encoding
  • Declared Properties

YYClassIvarInfo

  • 對應(yīng)Ivar數(shù)據(jù)結(jié)構(gòu)
  • 構(gòu)建方法也是從Ivar作為輸入?yún)?shù)
    - (instancetype)initWithIvar:(Ivar)ivar;
  • 用到的系統(tǒng)API
    const char *ivar_getName(Ivar v);
    const char *ivar_getTypeEncoding(Ivar v);
    ptrdiff_t ivar_getOffset(Ivar v);

ptrdiff_t是C/C++標(biāo)準(zhǔn)庫中定義的一個與機(jī)器相關(guān)的數(shù)據(jù)類型。ptrdiff_t類型變量通常用來保存兩個指針減法操作的結(jié)果。ptrdiff_t定義在stddef.h(cstddef)這個文件內(nèi)忆家。ptrdiff_t通常被定義為long int類型犹菇。
ptrdiff_t定義在C99標(biāo)準(zhǔn)中。

YYClassMethodInfo

  • 對應(yīng)Method數(shù)據(jù)結(jié)構(gòu)
  • - (instancetype)initWithMethod:(Method)method;通過Method創(chuàng)建
  • 用到的系統(tǒng)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

  • 對應(yīng)struct objc_property_t數(shù)據(jù)結(jié)構(gòu)
  • - (instancetype)initWithProperty:(objc_property_t)property;
  • 用到的系統(tǒng)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);
  • 如果是類芽卿,通過函數(shù)Class objc_getClass(const char *name);獲取屬性的類型信息
  • getter和setter函數(shù)揭芍,如果沒有,會生成默認(rèn)的
if (_name.length) {
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }

YYClassInfo

  • 對應(yīng)Class數(shù)據(jù)結(jié)構(gòu)
  • + (instancetype)classInfoWithClass:(Class)cls;
  • 這里用了兩個靜態(tài)的字典來存儲類信息卸例。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);
});
  • 這里也用到了線程保護(hù)摊滔,使用的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);
}
  • 用到的系統(tǒng)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阴绢,簡單枚舉,沒有位操作
  • 對應(yīng)屬性中的Foundation類型艰躺,比如NSString呻袭,NSNumber等
  • YYEncodingType是包括基本類型,比如int腺兴,double等
  • YYEncodingType中的YYEncodingTypeObject進(jìn)一步細(xì)分左电,可以得到相應(yīng)的YYEncodingNSType
  • 相關(guān)的靜態(tài)全局函數(shù)是static force_inline YYEncodingNSType YYClassGetNSType(Class cls);

YYNSDateFromString

  • 這是一個靜態(tài)全局函數(shù)
  • 功能是將NSString轉(zhuǎn)化為對應(yīng)的NSDate
  • 由于日期format的種類很多,這里引入了一個block的數(shù)組
  • 將NSString的字符個數(shù)作為數(shù)據(jù)的標(biāo)號页响,這種思維比較奇特
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進(jìn)一步信息整合
  • 這里對屬性的類型進(jìn)行判斷:
    是一個Foundation類型篓足,還是一個C數(shù)值類型,或者是一個容器類型等

_YYModelMeta

  • YYClassInfo進(jìn)一步信息整合`
  • 遍歷類所有的屬性闰蚕,直到根類
// 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;
  • 處理代理函數(shù)modelPropertyBlacklist---黑名單
  • 處理代理函數(shù)modelPropertyWhitelist---白名單
  • 處理代理函數(shù)modelContainerPropertyGenericClass---容器類型指定栈拖,支持class類型和字符串的類名
  • 將類所有的屬性轉(zhuǎn)換為_YYModelPropertyMeta數(shù)組
  • 處理代理函數(shù)modelCustomPropertyMapper---屬性和JSON鍵名稱的對應(yīng)關(guān)系。這種對應(yīng)關(guān)系支持.格式的鏈?zhǔn)疥P(guān)系和一對多的數(shù)組没陡。保存在相應(yīng)的_YYModelPropertyMeta成員中涩哟。
  • 做標(biāo)記,判斷用戶是否自定義了以下協(xié)議函數(shù):
    modelCustomWillTransformFromDictionary
    modelCustomTransformFromDictionary
    modelCustomTransformToDictionary
    modelCustomClassForDictionary

ModelSetContext

typedef struct {
    void *modelMeta;  ///< _YYModelMeta
    void *model;      ///< id (self)
    void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
  • 這是一個結(jié)構(gòu)體
  • 將類信息(_YYModelMeta)盼玄,類(model)贴彼,JSON(NSDictionary)等放在一起。
  • model --- modelMeta --- dictionary埃儿;相互轉(zhuǎn)化的兩種結(jié)構(gòu)通過一個中間過渡數(shù)據(jù)結(jié)構(gòu)器仗,整合在一起
  • 類型都是void *,是C的指針

函數(shù)調(diào)用流程

JSON -》Model

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

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

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

Model -》JSON

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

id的類型是NSDictionary or NSArray暴心,根據(jù)實(shí)際情況指定

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

都是基于- (nullable id)yy_modelToJSONObject;的結(jié)果做格式轉(zhuǎn)換杂拨,這里用到了系統(tǒng)API:+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;

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

幾個知識點(diǎn)

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);
    }```
  • 這里調(diào)用的時候多了一個(void *)弹沽,多了一步強(qiáng)制轉(zhuǎn)換檀夹,本質(zhì)上都是為了解決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函數(shù)來實(shí)現(xiàn)策橘,objc_msgSendSEL參數(shù)基本上都是meta->_setter

case1:數(shù)字類型

  • 實(shí)現(xiàn)過程在全局函數(shù)中ModelSetNumberToProperty
  • 細(xì)分為布爾型炸渡,8位,16位丽已,32位蚌堵,64位整數(shù),浮點(diǎn)數(shù)等等各種具體類型

case2:Foundation類型

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

  2. Model屬性類型是NSDecimalNumber;對value(id)的類型是NSDecimalNumber嘁灯、NSNumber泻蚊、NSString的情況做了容錯處理

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

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

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

  6. Model屬性類型是NSDictionaryNSSet羹奉、NSArray等容器類型時秒旋,對容器中每個成員調(diào)用函數(shù)yy_modelSetWithDictionary,一層層深入下去诀拭。

case3:其他類型

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

  1. Model屬性類型是id對象類型時迁筛,調(diào)用函數(shù)yy_modelSetWithDictionary,一層層深入下去炫加。

  2. Model屬性類型是Class類型時瑰煎,如果value是NSString,則調(diào)用函數(shù)NSClassFromString進(jìn)行轉(zhuǎn)化俗孝,然后設(shè)置酒甸。如果是其他類型,則判斷其“元類”class_isMetaClass是否存在赋铝。存在插勤,則直接設(shè)置

  3. Model屬性類型是SEL類型時,如果value是NSString,則調(diào)用函數(shù)NSSelectorFromString進(jìn)行轉(zhuǎn)化农尖,然后設(shè)置析恋。

  4. Model屬性類型是Block類型時,將value強(qiáng)制轉(zhuǎn)換為void (^)()進(jìn)行設(shè)置盛卡。

  5. Model屬性類型是struct助隧、unionchar[10]等類型時滑沧,如果value是NSValue并村,則調(diào)用函數(shù)- (void)setValue:(nullable id)value forKey:(NSString *)key;進(jìn)行設(shè)置。

  6. Model屬性類型是void*滓技、char*等類型時哩牍,如果value是NSValue,則將value強(qiáng)制轉(zhuǎn)換為NSValue令漂,取屬性pointerValue進(jìn)行設(shè)置膝昆。進(jìn)行設(shè)置。

個人意見

  1. 看上去只有兩個文件叠必,但是類有很多荚孵,用了很多的內(nèi)部類。這種方式不是很認(rèn)同挠唆。還是推薦一個類一個源文件的方式处窥。當(dāng)然嘱吗,這里的場景是高內(nèi)聚的一個整體玄组,本來也是把一個類的各子成員(都是struct),還是比較合適的谒麦。
  2. 將所有的頭文件都?xì)w總為一個YYMode.h俄讹,這種方式是非常好的,推薦使用绕德。
  3. 對于NSArray這種集合提供方便方法患膛;對于JSON對象,采用id類型耻蛇,支持NSDictionary, NSString, NSData三種類型踪蹬;在處理的時候,統(tǒng)一為NSDictionary臣咖。這種方式跃捣,統(tǒng)籌考慮了實(shí)現(xiàn)和使用的方便性,只是增加了幾層函數(shù)調(diào)用夺蛇,值得推薦疚漆。
  4. 協(xié)議的定義、使用者、實(shí)現(xiàn)者都是同一個(self娶聘,Model自己)闻镶,這里是特殊的使用場景。
    一般情況下應(yīng)該分3個文件(協(xié)議定義丸升,使用者铆农,實(shí)現(xiàn)者)或者2個文件(協(xié)議的定義放在使用者的文件中)。
  5. 采用協(xié)議的設(shè)計方式狡耻,讓使用者對特殊使用場景做自定義顿涣,值得推薦
  6. 將_YYModelMeta(runtime中各種struct對應(yīng)的類)作為轉(zhuǎn)換的中間載體,思維很巧妙
  7. if后面只有一個語句酝豪,省略了{}涛碑,這種習(xí)慣不是很好。
    if (num) [num class]; // hold the number

參考文章

YYModel
iOS JSON 模型轉(zhuǎn)換庫評測
JSON 數(shù)據(jù)格式
Objective-C Runtime Programming Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孵淘,一起剝皮案震驚了整個濱河市蒲障,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘫证,老刑警劉巖揉阎,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異背捌,居然都是意外死亡毙籽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門毡庆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坑赡,“玉大人,你說我怎么就攤上這事么抗∫惴瘢” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵蝇刀,是天一觀的道長螟加。 經(jīng)常有香客問我,道長吞琐,這世上最難降的妖魔是什么捆探? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮站粟,結(jié)果婚禮上黍图,老公的妹妹穿的比我還像新娘。我一直安慰自己卒蘸,他們只是感情好雌隅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布翻默。 她就那樣靜靜地躺著,像睡著了一般恰起。 火紅的嫁衣襯著肌膚如雪修械。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天检盼,我揣著相機(jī)與錄音肯污,去河邊找鬼。 笑死吨枉,一個胖子當(dāng)著我的面吹牛蹦渣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播貌亭,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼柬唯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了圃庭?” 一聲冷哼從身側(cè)響起锄奢,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剧腻,沒想到半個月后拘央,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡书在,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年灰伟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儒旬。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏账,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出义矛,到底是詐尸還是另有隱情发笔,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布凉翻,位于F島的核電站,受9級特大地震影響捻激,放射性物質(zhì)發(fā)生泄漏制轰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一胞谭、第九天 我趴在偏房一處隱蔽的房頂上張望垃杖。 院中可真熱鬧,春花似錦丈屹、人聲如沸调俘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彩库。三九已至肤无,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骇钦,已是汗流浹背宛渐。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眯搭,地道東北人窥翩。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鳞仙,于是被迫代替她去往敵國和親寇蚊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉棍好,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理幔荒,服務(wù)發(fā)現(xiàn),斷路器梳玫,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 終于把前面的base文件夾簡簡單單的看了一遍爹梁,終于可以回到正片上來了,保證不爛尾提澎。 項目天天用yymodel解析數(shù)...
    充滿活力的早晨閱讀 1,358評論 1 0
  • Objective-C語言是一門動態(tài)語言姚垃,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運(yùn)行時來處理。這種動態(tài)語言的...
    有一種再見叫青春閱讀 577評論 0 3
  • Objective-C語言是一門動態(tài)語言盼忌,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運(yùn)行時來處理积糯。這種動態(tài)語言...
    tigger丨閱讀 1,382評論 0 8