手把手帶你擼一個(gè) YYModel 的精簡(jiǎn)版

讀完這篇文章你可以自己寫一個(gè) YYModel 這樣的神器叹括,這篇文章類似一個(gè)源碼解析枯芬,但不同的是,它不光光是解析碰辅,更是實(shí)戰(zhàn)懂昂,因?yàn)槲矣X得學(xué)習(xí)一個(gè)東西必須要自己寫一遍才算是真的學(xué)了一遍,否則即便是讀完了源碼印象還是不會(huì)太深刻没宾,so凌彬,開始吧。

注:為了簡(jiǎn)單起見循衰,我的例子只是實(shí)現(xiàn)了一個(gè)精簡(jiǎn)的版本铲敛,YYModel 有很多功能,我這里就實(shí)現(xiàn)了一個(gè)核心的功能会钝,JSON -> Model伐蒋。

注:文章的最后有完整的代碼

從JSON映射到Model的原理

想一下平時(shí)我們是怎么使用類似這樣子的庫(kù)的,當(dāng)我們有一個(gè)JSON的時(shí)候迁酸,我們把所有JSON的字段(比如name先鱼、page)全部寫成對(duì)應(yīng)的類中的屬性。然后庫(kù)會(huì)自動(dòng)把你JSON對(duì)應(yīng)字段的值賦值到這些對(duì)應(yīng)的屬性里去奸鬓。屬性我們用 @property 來(lái)定義焙畔,就意味著編譯器會(huì)幫你生成對(duì)應(yīng)的get``set方法,我們使用的 . 其實(shí)也是在調(diào)用get``set方法來(lái)實(shí)現(xiàn)賦值的全蝶。在 Objective-C 中有一個(gè)著名的函數(shù) objc_msgSend(...) 我們所有的類似 [obj method] 的方法調(diào)用(發(fā)送消息)都會(huì)被轉(zhuǎn)換成 objc_msgSend(...) 的方式來(lái)調(diào)用闹蒜。(具體這個(gè)函數(shù)怎么用后面再說(shuō))

所以對(duì)于一個(gè)庫(kù)來(lái)說(shuō),要調(diào)用你這個(gè) Model 的 set 方法抑淫,用 objc_msgSend(...) 會(huì)容易的多绷落,所以JSON映射到Model的原理其實(shí)就是調(diào)用這個(gè)函數(shù)而已。

所以整個(gè)流程就是始苇,你給我一個(gè) Model 類砌烁,我會(huì)用 runtime 提供的各種函數(shù)來(lái)拿到你所有的屬性和對(duì)應(yīng)的get``set,判斷完相應(yīng)的類型以后催式,調(diào)用objc_msgSend(...)函喉。說(shuō)起來(lái)真的非常簡(jiǎn)單,做起來(lái)就有點(diǎn)麻煩...

前期的準(zhǔn)備工作

為了后面的方便荣月,我們需要把一些關(guān)鍵的東西封裝起來(lái)管呵,我們需要單獨(dú)封裝 ivar property method,也就是實(shí)例變量哺窄、屬性捐下、方法,但事實(shí)上我們的這個(gè)精簡(jiǎn)版的YYModel并不需要 method ivar 的封裝萌业,為了保證完整性坷襟,我還是打算寫下來(lái)。

封裝 ivar

先來(lái)封裝 ivar生年,看一下頭文件 CPClassIvarInfo.h(YYModel只有4個(gè)文件婴程,兩個(gè) .h 兩個(gè) .m 我為了讓代碼看起來(lái)更清楚,所以我自己在重寫 YYModel 的時(shí)候把所有可以拆出來(lái)的類都分別拆成了一對(duì).h .m)并把前綴改成了 CP 意思是 copy抱婉。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"

@interface CPClassIvarInfo : NSObject

@property (nonatomic, assign, readonly) Ivar ivar;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *typeEncoding;
@property (nonatomic, assign, readonly) CPEncodingType type;

- (instancetype)initWithIvar:(Ivar)ivar;

@end

Ivar 代表一個(gè)實(shí)例變量档叔,你所有和實(shí)例變量有關(guān)的操作都必須要把 Ivar 傳進(jìn)去,等一下就能看到蒸绩。

name 是這個(gè)實(shí)例變量的變量名

typeEncoding 是對(duì)類型的編碼蹲蒲,具體可以看這里 對(duì)于不同的類型就會(huì)有對(duì)應(yīng)的編碼,比如 int 就會(huì)變編碼成 i侵贵,可以用 @encode(int)這樣的操作來(lái)看一個(gè)類型的編碼届搁。

type 是一個(gè)自定義的枚舉,它描述了 YYMode 規(guī)定的類型窍育。

一個(gè)強(qiáng)大的枚舉

然后重新再創(chuàng)建一個(gè)文件(CPCommon)卡睦,作為一個(gè)公共的文件 CPEncodingType 這個(gè)枚舉就寫在這里。

我們要?jiǎng)?chuàng)建的這個(gè)枚舉需要一口氣表示三種不同的類型漱抓,一種用于普通的類型上(int double object)表锻,一種用來(lái)表示關(guān)鍵詞(const),一種表示 Property 的屬性(Nonatomic weak retain)乞娄。

我們可以用位運(yùn)算符來(lái)搞定這三種類型瞬逊,用8位的枚舉值來(lái)表示第一種显歧,16位的表示第二種,24位的表示第三種确镊,然后為了區(qū)別這三種類型都屬于多少位的士骤,我們可以分別搞三個(gè) mask ,做一個(gè)該類型和某一個(gè) mask 的與(&)的操作就可以知道這個(gè)類型是具體是哪一個(gè)類型了蕾域,例子在后面拷肌。

這個(gè)枚舉我們可以這樣定義:

typedef NS_OPTIONS(NSUInteger, CPEncodingType) {
    CPEncodingTypeMask       = 0xFF, //8 bit
    CPEncodingTypeUnknown    = 0, 
    CPEncodingTypeVoid       = 1, 
    CPEncodingTypeBool       = 2, 
    CPEncodingTypeInt8       = 3, 
    CPEncodingTypeUInt8      = 4, 
    CPEncodingTypeInt16      = 5, 
    CPEncodingTypeUInt16     = 6, 
    CPEncodingTypeInt32      = 7, 
    CPEncodingTypeUInt32     = 8, 
    CPEncodingTypeInt64      = 9, 
    CPEncodingTypeUInt64     = 10,
    CPEncodingTypeFloat      = 11,
    CPEncodingTypeDouble     = 12,
    CPEncodingTypeLongDouble = 13,
    CPEncodingTypeObject     = 14,
    CPEncodingTypeClass      = 15,
    CPEncodingTypeSEL        = 16,
    CPEncodingTypeBlock      = 17,
    CPEncodingTypePointer    = 18,
    CPEncodingTypeStruct     = 19,
    CPEncodingTypeUnion      = 20,
    CPEncodingTypeCString    = 21,
    CPEncodingTypeCArray     = 22,
    
    CPEncodingTypeQualifierMask   = 0xFF00,  //16 bit
    CPEncodingTypeQualifierConst  = 1 << 8,  
    CPEncodingTypeQualifierIn     = 1 << 9,  
    CPEncodingTypeQualifierInout  = 1 << 10, 
    CPEncodingTypeQualifierOut    = 1 << 11, 
    CPEncodingTypeQualifierBycopy = 1 << 12, 
    CPEncodingTypeQualifierByref  = 1 << 13, 
    CPEncodingTypeQualifierOneway = 1 << 14,
    
    CPEncodingTypePropertyMask         = 0xFF0000, // 24 bit
    CPEncodingTypePropertyReadonly     = 1 << 16, 
    CPEncodingTypePropertyCopy         = 1 << 17, 
    CPEncodingTypePropertyRetain       = 1 << 18, 
    CPEncodingTypePropertyNonatomic    = 1 << 19, 
    CPEncodingTypePropertyWeak         = 1 << 20, 
    CPEncodingTypePropertyCustomGetter = 1 << 21, 
    CPEncodingTypePropertyCustomSetter = 1 << 22, 
    CPEncodingTypePropertyDynamic      = 1 << 23,
};

比如有一個(gè)類型是這樣的

CPEncodingType type = CPEncodingTypeDouble;

假設(shè)我們并不知道它是 CPEncodingTypeDouble 類型,那我們要怎么樣才能知道它是什么類型呢旨巷?只要這樣:

NSLog(@"%lu",type & CPEncodingTypeMask);

輸出: 12

在枚舉的定義中

CPEncodingTypeDouble     = 12,

假設(shè)這個(gè)枚舉值有很多種混在一起

CPEncodingType type = CPEncodingTypeDouble | CPEncodingTypePropertyRetain;
NSLog(@"%lu",type & CPEncodingTypePropertyMask); //輸出 262144 (1<<18的十進(jìn)制表示)
NSLog(@"%lu",type & CPEncodingTypeMask); //輸出 12

可能有人知道這種神奇的用法巨缘,但在我讀YYModel之前我沒用過這種方法(技術(shù)比較菜)。

然后還有一個(gè)函數(shù)采呐,這個(gè)函數(shù)可以把類型編碼(Type Encoding)轉(zhuǎn)換成剛才的枚舉值若锁,很簡(jiǎn)單卻很長(zhǎng)的一個(gè)函數(shù):

CPEncodingType CPEncodingGetType(const char *typeEncoding) {
    char *type = (char *)typeEncoding;
    if (!type) return CPEncodingTypeUnknown;
    size_t len = strlen(type);
    if (len == 0) return CPEncodingTypeUnknown;
    
    CPEncodingType qualifier = 0;
    bool prefix = true;
    while (prefix) {
        switch (*type) {
            case 'r': {
                qualifier |= CPEncodingTypeQualifierConst;
                type++;
            } break;
            case 'n': {
                qualifier |= CPEncodingTypeQualifierIn;
                type++;
            } break;
            case 'N': {
                qualifier |= CPEncodingTypeQualifierInout;
                type++;
            } break;
            case 'o': {
                qualifier |= CPEncodingTypeQualifierOut;
                type++;
            } break;
            case 'O': {
                qualifier |= CPEncodingTypeQualifierBycopy;
                type++;
            } break;
            case 'R': {
                qualifier |= CPEncodingTypeQualifierByref;
                type++;
            } break;
            case 'V': {
                qualifier |= CPEncodingTypeQualifierOneway;
                type++;
            } break;
            default: { prefix = false; } break;
        }
    }
    
    len = strlen(type);
    if (len == 0) return CPEncodingTypeUnknown | qualifier;
    
    switch (*type) {
        case 'v': return CPEncodingTypeVoid | qualifier;
        case 'B': return CPEncodingTypeBool | qualifier;
        case 'c': return CPEncodingTypeInt8 | qualifier;
        case 'C': return CPEncodingTypeUInt8 | qualifier;
        case 's': return CPEncodingTypeInt16 | qualifier;
        case 'S': return CPEncodingTypeUInt16 | qualifier;
        case 'i': return CPEncodingTypeInt32 | qualifier;
        case 'I': return CPEncodingTypeUInt32 | qualifier;
        case 'l': return CPEncodingTypeInt32 | qualifier;
        case 'L': return CPEncodingTypeUInt32 | qualifier;
        case 'q': return CPEncodingTypeInt64 | qualifier;
        case 'Q': return CPEncodingTypeUInt64 | qualifier;
        case 'f': return CPEncodingTypeFloat | qualifier;
        case 'd': return CPEncodingTypeDouble | qualifier;
        case 'D': return CPEncodingTypeLongDouble | qualifier;
        case '#': return CPEncodingTypeClass | qualifier;
        case ':': return CPEncodingTypeSEL | qualifier;
        case '*': return CPEncodingTypeCString | qualifier;
        case '^': return CPEncodingTypePointer | qualifier;
        case '[': return CPEncodingTypeCArray | qualifier;
        case '(': return CPEncodingTypeUnion | qualifier;
        case '{': return CPEncodingTypeStruct | qualifier;
        case '@': {
            if (len == 2 && *(type + 1) == '?')
                return CPEncodingTypeBlock | qualifier;
            else
                return CPEncodingTypeObject | qualifier;
        }
        default: return CPEncodingTypeUnknown | qualifier;
    }
}

很簡(jiǎn)單,不用多講了斧吐。

回到 CPClassIvarInfo 剛才我們只給出了頭文件拴清,現(xiàn)在看一下實(shí)現(xiàn)。

- (instancetype)initWithIvar:(Ivar)ivar {
    if (!ivar){
        return nil;
    }
    
    self = [super init];
    if (self){
        _ivar = ivar;
        const char *name = ivar_getName(ivar);
        if (name){
            _name = [NSString stringWithUTF8String:name];
        }
        const char *typeEncoding = ivar_getTypeEncoding(ivar);
        if (typeEncoding){
            _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
            _type = CPEncodingGetType(typeEncoding);
        }
    }
    return self;
}

只有一個(gè)方法会通,這里就用到了兩個(gè) runtime 函數(shù) ivar_getName(ivar)ivar_getTypeEncoding(ivar) 傳入 ivar 就行口予。

封裝Method

然后看一下對(duì)于 Method 的封裝,看一下頭文件(CPClassMethodInfo.h)

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

NS_ASSUME_NONNULL_BEGIN

@interface CPClassMethodInfo : 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;

NS_ASSUME_NONNULL_END

@end

Objective-C 的 Optional

NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 是成對(duì)出現(xiàn)的涕侈,因?yàn)?Swift 可以和 Objective-C 混用沪停,但是 Swift 有 Optional 類型,而 Objective-C 沒有這樣的概念裳涛,為了和 Swift 保持一致木张,現(xiàn)在 Objective-C 有了 _Nullable 可空 _Nonnull不可空這樣的關(guān)鍵字,這兩個(gè)關(guān)鍵字可以在變量端三、方法返回值舷礼、方法參數(shù)上使用,比如:

@property (nonatomic, strong) NSString  * _Nonnull string;

- (NSString * _Nonnull)method:(NSString *_Nonnull)string;

還有另外一對(duì) nullable nonnull郊闯,它們可以這樣用

@property (nullable, nonatomic, strong) NSString  *string;

- (nullable NSString *)method:(nullable NSString *)string;

對(duì)了妻献,這些關(guān)鍵詞只能用在指針上,其他類型是不能用的团赁。

當(dāng)你一旦在某個(gè)地方寫上關(guān)鍵詞 nullable的時(shí)候育拨,編譯器就會(huì)提出警告,Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified) 然后你就可以加上NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END來(lái)表示只有我標(biāo)記為 nullable 的地方才可空欢摄,其余地方都是 nonnull熬丧。

回到剛才的頭文件代碼,method 表示一個(gè)方法

name 很明顯就是方法名了

selimp 是一個(gè)對(duì)應(yīng)關(guān)系怀挠,一個(gè)對(duì)象的所有方法都會(huì)保存在一張表里,通過 sel 就能找到這個(gè)方法的 imp,我講的可能有點(diǎn)簡(jiǎn)單,如果想要深入的了解可以查一下文檔或者博客。

typeEncoding 又是一個(gè)編碼播揪,這里是參數(shù)和返回值的編碼

returnTypeEncoding 返回值的編碼

argumentTypeEncodings 所有參數(shù)的編碼

實(shí)現(xiàn)還是很簡(jiǎn)單

- (instancetype)initWithMethod:(Method)method {
    if (!method) {
        return nil;
    }
    
    self = [super init];
    if (self){
        _method = method;
        _sel = method_getName(method);
        _imp = method_getImplementation(method);
        const char *name = sel_getName(_sel);
        if (name) {
            _name = [NSString stringWithUTF8String:name];
        }
        const char *typeEncoding = method_getTypeEncoding(method);
        if (typeEncoding) {
            _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        }
        char *returnTypeEncoding = method_copyReturnType(method);
        if (returnTypeEncoding) {
            _returnTypeEncoding = [NSString stringWithUTF8String:returnTypeEncoding];
            free(returnTypeEncoding);
        }
        
        //得到參數(shù)的數(shù)目,遍歷取得所有參數(shù)的類型
        unsigned int count = method_getNumberOfArguments(method);
        if (count > 0) {
            NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:10];
            for (unsigned int i = 0; i < count; i++) {
                char *argumentsType = method_copyArgumentType(method, i);
                NSString *type = argumentsType ? [NSString stringWithUTF8String:argumentsType] : nil;
                [types addObject:type ? type : @""];
                if (argumentsType) {
                    free(argumentsType);
                }
            }
            _argumentTypeEncodings = types;
        }
        
    }
    
    return self;
}

和前面套路一樣。

封裝 Property

老樣子,看頭

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"

NS_ASSUME_NONNULL_BEGIN

@interface CPClassPropertyInfo : NSObject

@property (nonatomic, assign, readonly) objc_property_t property;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) CPEncodingType type;
@property (nonatomic, strong, readonly) NSString *typdEncoding;
@property (nonatomic, strong, readonly) NSString *ivarName;
@property (nullable, nonatomic, assign, readonly) Class cls;
@property (nonatomic, assign, readonly) SEL getter;
@property (nonatomic, assign, readonly) SEL setter;

- (instancetype)initWithProperty:(objc_property_t)property;

NS_ASSUME_NONNULL_END

@end

這是在精簡(jiǎn)版的YYModel中會(huì)用到的一個(gè)類,這里尤其要注意的是typetypdEncoding兩個(gè)屬性冯吓,希望讀者能夠仔細(xì)調(diào)試一下倘待,看一下主要的一段代碼:

CPEncodingType type = 0;
unsigned int outCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
//遍歷所有的Property的屬性
for (unsigned int i = 0; i < outCount; i++) {
    switch (attrs[i].name[0]) {
        case 'T':
            if (attrs[i].value) {
                _typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
                type = CPEncodingGetType(attrs[i].value);
                
                if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
                    //如果該類型為一個(gè)對(duì)象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString组贺,目的是為了得到這個(gè)類的 Class
                    size_t len = strlen(attrs[i].value);
                    if (len > 3) {
                        char name[len - 2];
                        name[len - 3] = '\0';
                        memcpy(name, attrs[i].value + 2, len - 3);
                        _cls = objc_getClass(name);
                    }
                }
            }
            break;
            
            case 'V':
                if (attrs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                }
            break;
            
        case 'R':
            type |= CPEncodingTypePropertyReadonly;
            break;
            
        case 'C':
            type |= CPEncodingTypePropertyCopy;
            break;
            
        case '&':
            type |= CPEncodingTypePropertyRetain;
            break;
            
        case 'N':
            type |= CPEncodingTypePropertyNonatomic;
            break;
            
        case 'D':
            type |= CPEncodingTypePropertyDynamic;
            break;
            
        case 'W':
            type |= CPEncodingTypePropertyWeak;
            break;
            
        case  'G':
            type |= CPEncodingTypePropertyCustomGetter;
            if (attrs[i].value) {
                _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
            }
            break;
        
        case 'S':
            type |= CPEncodingTypePropertyCustomSetter;
            if (attrs[i].value) {
                _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
            }
            break;
            
        default: break;
    }
}

我們通過property_copyAttributeList這個(gè)函數(shù)得到一個(gè)指向一個(gè)結(jié)構(gòu)體objc_property_attribute_t的指針凸舵,這個(gè)結(jié)構(gòu)體的結(jié)構(gòu)如下:

typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

說(shuō)是一個(gè)指針,其實(shí)它是一個(gè)結(jié)構(gòu)體數(shù)組失尖,指針指向的其實(shí)是這個(gè)數(shù)組第一個(gè)元素啊奄。

這個(gè)結(jié)構(gòu)體表示的是一個(gè) Property 的屬性,關(guān)于 Property 的類型編碼可以看這里

要說(shuō)清這個(gè)數(shù)組里每一個(gè)結(jié)構(gòu)體元素的namevalue都存了什么掀潮,我們可以看一下下面這段代碼:

Class cls = objc_getClass("CPBook");
objc_property_t property = class_getProperty(cls, "name");
const char* attr = property_getAttributes(property);
NSLog(@"%s",attr);

這里比如有一個(gè)類是 CPBook 菇夸,我們通過這個(gè)類的 Class 來(lái)拿到一個(gè)叫做 name 的 Property,然后在拿到這個(gè) Property 所有屬性仪吧,輸出的結(jié)果是 T@"NSString",&,N,V_name

其實(shí)庄新,我們用和上面一樣返回一個(gè)結(jié)構(gòu)體數(shù)組的方式來(lái)獲取這個(gè) Property 的屬性的話,那么這個(gè)結(jié)構(gòu)體應(yīng)該會(huì)有4個(gè)元素薯鼠。

第一個(gè)元素 name = T择诈,value = @"NSString",第二個(gè)元素 name = &出皇,value 沒有值羞芍,第三個(gè)元素 name = Nvalue 仍然沒有值郊艘,第四個(gè)元素 name = V荷科,value = _name。不信可以運(yùn)行一下下面的代碼來(lái)看看纱注。

Class cls = objc_getClass("CPBook");
unsigned int acount;
objc_property_t *prop = class_copyPropertyList(cls, &acount);
    
objc_property_attribute_t *attr1 = property_copyAttributeList(prop[2], &acount);
NSLog(@"%s",attr1[0].name);
NSLog(@"%s",attr1[0].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[1].name);
NSLog(@"%s",attr1[1].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[2].name);
NSLog(@"%s",attr1[2].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[3].name);
NSLog(@"%s",attr1[3].value);

至于 V N & 這樣的符號(hào)是什么意思步做,可以打開上面給出的鏈接自己看一下文檔,一看便知奈附。

這樣一來(lái)在 switch 分支中全度,只要匹配到 T 就能得到這個(gè) Property 的類型是什么,這樣就可以得到這個(gè)類型的 Type Encoding斥滤,并且能夠得到該類的 Class将鸵。只要匹配到 V 就能得到這個(gè) Property 實(shí)例變量名勉盅。

該類全部代碼如下:

- (instancetype)initWithProperty:(objc_property_t)property {
    if (!property) {
        return nil;
    }
    
    self = [super init];
    if (self) {
        _property = property;
        const char *name = property_getName(property);
        if (name) {
            _name = [NSString stringWithUTF8String:name];
        }
        
        CPEncodingType type = 0;
        unsigned int outCount;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
        //遍歷所有的Property的屬性
        for (unsigned int i = 0; i < outCount; i++) {
            switch (attrs[i].name[0]) {
                case 'T':
                    if (attrs[i].value) {
                        _typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
                        type = CPEncodingGetType(attrs[i].value);
                        
                        if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
                            //如果該類型為一個(gè)對(duì)象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString顶掉,目的是為了得到這個(gè)類的 Class
                            size_t len = strlen(attrs[i].value);
                            if (len > 3) {
                                char name[len - 2];
                                name[len - 3] = '\0';
                                memcpy(name, attrs[i].value + 2, len - 3);
                                _cls = objc_getClass(name);
                            }
                        }
                    }
                    break;
                    
                    case 'V':
                        if (attrs[i].value) {
                            _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                        }
                    break;
                    
                case 'R':
                    type |= CPEncodingTypePropertyReadonly;
                    break;
                    
                case 'C':
                    type |= CPEncodingTypePropertyCopy;
                    break;
                    
                case '&':
                    type |= CPEncodingTypePropertyRetain;
                    break;
                    
                case 'N':
                    type |= CPEncodingTypePropertyNonatomic;
                    break;
                    
                case 'D':
                    type |= CPEncodingTypePropertyDynamic;
                    break;
                    
                case 'W':
                    type |= CPEncodingTypePropertyWeak;
                    break;
                    
                case  'G':
                    type |= CPEncodingTypePropertyCustomGetter;
                    if (attrs[i].value) {
                        _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                    }
                    break;
                
                case 'S':
                    type |= CPEncodingTypePropertyCustomSetter;
                    if (attrs[i].value) {
                        _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                    }
                    break;
                    
                default: break;
            }
        }
        
        if (attrs) {
            free(attrs);
            attrs = NULL;
        }
        
        _type = type;
        
        if (_name.length) {
            if (!_getter) {
                _getter = NSSelectorFromString(_name);
            }
            if (!_setter) {
                _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
            }
        }
    }
    
    return self;
}

這樣一來(lái)草娜,我們就有了 ivar Method Property 的封裝類。接下來(lái)痒筒,我們需要一個(gè)叫做CPClassInfo的類宰闰,來(lái)封裝一些類的信息,并且把以上三個(gè)類也封裝進(jìn)去簿透,用來(lái)描述整個(gè)類移袍。

封裝 Class

繼續(xù)看頭:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@class CPClassIvarInfo;
@class CPClassMethodInfo;
@class CPClassPropertyInfo;

NS_ASSUME_NONNULL_BEGIN

@interface CPClassInfo : NSObject

@property (nonatomic, assign, readonly) Class cls;
@property (nonatomic, assign, readonly) Class superClass;
@property (nonatomic, assign, readonly) Class metaClass;
@property (nonatomic, readonly) BOOL isMeta;
@property (nonatomic, strong, readonly) NSString *name;
@property (nullable, nonatomic, strong, readonly) CPClassInfo *superClassInfo;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassIvarInfo *> *ivarInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassMethodInfo *> *methodInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, CPClassPropertyInfo *> *propertyInfos;

- (void)setNeedUpadte;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;

NS_ASSUME_NONNULL_END

@end

Class 類型用來(lái)描述一個(gè)類,你可以使用

model.class
[model class]
[CPTestModel class]
object_getClass(model)

等方法來(lái)取到這個(gè) Class老充∑系粒·注意object_getClass()和其他方式 有些不同具體看這里

其余的 Property 不用多介紹了,看到它們的名字就大概能猜到干嘛的了啡浊。

最后的幾個(gè) NSDictionary 用來(lái)存所有的 ivar Method Property觅够。

有些時(shí)候,一個(gè)類有可能被更改巷嚣,可能改掉了方法或者是 Property喘先,那么這時(shí)候應(yīng)該通知CPClassInfo來(lái)重新獲取到更改過后的類的信息。所以我們有兩個(gè)相關(guān)的方法來(lái)實(shí)現(xiàn)這個(gè)目的廷粒。

- (void)setNeedUpadte;
- (BOOL)needUpdate;

先來(lái)看一下初始化方法

- (instancetype)initWithClass:(Class)cls{
    if (!cls) {
        return nil;
    }
    self = [super init];
    if (self) {
        _cls = cls;
        _superClass = class_getSuperclass(cls);
        _isMeta = class_isMetaClass(cls);
        if (_isMeta) {
            _metaClass = objc_getMetaClass(class_getName(cls));
        }
        _name = NSStringFromClass(cls);
        [self _update];
        
        _superClassInfo = [self.class classInfoWithClass:_superClass];
    }
    return self;
}

你沒看錯(cuò)苹祟,這和頭文件定義的classInfoWithClass:不是一個(gè)方法,頭文件定義的那個(gè)方法用來(lái)緩存评雌,因?yàn)閷?shí)例化這個(gè)方法還是有點(diǎn)開銷的树枫,所以沒有必要每一次都去實(shí)例化。

這里有一個(gè) _update 方法景东,剛才說(shuō)過砂轻,如果這個(gè)類會(huì)在某一個(gè)時(shí)刻發(fā)生變化,應(yīng)該通知斤吐,收到通知后搔涝,我們?nèi)?zhí)行一些更新的操作,所以把會(huì)發(fā)生變化的一部分代碼單獨(dú)拿出來(lái)更好和措,現(xiàn)在看一下 _update 方法庄呈。

- (void)_update{
    _ivarInfos = nil;
    _propertyInfos = nil;
    _methodInfos = nil;
    
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(self.cls, &ivarCount);
    if (ivars) {
        _ivarInfos = [NSMutableDictionary new];
        for (unsigned int i = 0; i < ivarCount; i++) {
            CPClassIvarInfo *ivarInfo = [[CPClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (ivarInfo.name) {
                [_ivarInfos setValue:ivarInfo forKey:ivarInfo.name];
            }
        }
        free(ivars);
    }
    
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(self.cls, &propertyCount);
    if (properties) {
        _propertyInfos = [NSMutableDictionary new];
        for (unsigned int i = 0; i < propertyCount; i++) {
            CPClassPropertyInfo *propertyInfo = [[CPClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (propertyInfo.name) {
                [_propertyInfos setValue:propertyInfo forKey:propertyInfo.name];
            }
        }
        free(properties);
    }
    
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(self.cls, &methodCount);
    if (methods) {
        _methodInfos = [NSMutableDictionary new];
        for (unsigned int i = 0; i < methodCount; i++) {
            CPClassMethodInfo *methodInfo = [[CPClassMethodInfo alloc] initWithMethod:methods[i]];
            if (methodInfo.name) {
                [_methodInfos setValue:methodInfo forKey:methodInfo.name];
            }
        }
        free(methods);
    }
    
    if (!_ivarInfos) {
        _ivarInfos = @{};
    }
    if (!_methodInfos) {
        _methodInfos = @{};
    }
    if (!_propertyInfos) {
        _propertyInfos = @{};
    }
    _needUpdate = NO;
}

其實(shí)這個(gè)方法就是拿到一個(gè)類所有的 ivar Method Property ,一個(gè)類發(fā)生變化是不是主要就是這三個(gè)玩意的變化派阱?

最后一行的 _needUpdate 是一個(gè)全局變量诬留,用來(lái)標(biāo)識(shí)是否發(fā)生的變化,它被定義在這里,以免暴露給外面文兑。

@implementation CPClassInfo{
    BOOL _needUpdate;
}

當(dāng)外界需要通知自己已經(jīng)發(fā)生變化或者查一下是否發(fā)生變化時(shí)就調(diào)用這兩個(gè)相關(guān)方法

- (BOOL)needUpdate {
    return _needUpdate;
}

- (void)setNeedUpadte {
    _needUpdate = YES;
}

現(xiàn)在來(lái)看一下classInfoWithClass:

+ (instancetype)classInfoWithClass:(Class)cls{
    if (!cls) {
        return nil;
    }
    
    static NSMutableDictionary *metaCache;
    static NSMutableDictionary *classCache;
    static dispatch_semaphore_t lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        metaCache = [NSMutableDictionary dictionary];
        classCache = [NSMutableDictionary dictionary];
        lock = dispatch_semaphore_create(1);
    });
    
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    CPClassInfo *info;
    if (class_isMetaClass(cls)) {
        info = [metaCache valueForKey:NSStringFromClass(cls)];
    } else {
        info = [classCache valueForKey:NSStringFromClass(cls)];
    }
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    
    if (!info) {
        info = [[CPClassInfo alloc] initWithClass:cls];
        if (info) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            if (info.isMeta) {
                [metaCache setValue:info forKey:NSStringFromClass(cls)];
            } else {
                [classCache setValue:info forKey:NSStringFromClass(cls)];
            }
            dispatch_semaphore_signal(lock);
        }
    }
    
    return info;
}

兩個(gè) NSMutableDictionary 都是用來(lái)緩存的盒刚,并聲明在了靜態(tài)區(qū),并且使用dispatch_once()來(lái)確保只會(huì)被初始化一次绿贞,然后我們需要保證線程安全因块,因?yàn)橛锌赡軙?huì)在多線程的場(chǎng)景里被用到,所以使用信號(hào)量dispatch_semaphore_t來(lái)搞定籍铁,信號(hào)量就像停車這樣的場(chǎng)景一樣涡上,如果發(fā)現(xiàn)車滿了,就等待拒名,一有空位就放行吩愧,也就是說(shuō),當(dāng)一個(gè)線程要進(jìn)入臨界區(qū)的時(shí)候靡狞,必須獲取一個(gè)信號(hào)量耻警,如果沒有問題就進(jìn)入臨界區(qū)隔嫡,這時(shí)另一個(gè)線程進(jìn)來(lái)了甸怕,也要獲取,發(fā)現(xiàn)信號(hào)量并沒有釋放腮恩,就繼續(xù)等待梢杭,直到前面一個(gè)信號(hào)量被釋放后,該線程才準(zhǔn)許進(jìn)入秸滴。我們可以使用dispatch_semaphore_wait()來(lái)獲取信號(hào)量武契,通過dispatch_semaphore_signal()來(lái)釋放信號(hào)量。

在這段代碼里荡含,我們首先確保要實(shí)例化的這個(gè)對(duì)象有沒有被緩存咒唆,用傳進(jìn)來(lái)的 cls 作為 key,如果緩存命中释液,那直接取出緩存全释,然后判斷一下,有沒有更新误债,如果有更新浸船,調(diào)用_update刷新一遍,返回寝蹈,否則直接返回李命。緩存沒有命中的話,還是乖乖的調(diào)用實(shí)例化方法箫老,然后緩存起來(lái)封字。

繼續(xù)封裝

CPModelPropertyMeta

先建一個(gè)文件,叫做 CPMeta.hCPMeta.m,我們要在這里寫兩個(gè)類周叮,一個(gè)是對(duì) Property 的再次封裝辩撑,一個(gè)是對(duì) Class 的再次封裝。

我直接把頭文件代碼全拿出來(lái)了:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CPCommon.h"
@class CPClassInfo;
@class CPClassPropertyInfo;

typedef NS_ENUM (NSUInteger, CPEncodingNSType) {
    CPEncodingTypeNSUnknown = 0,
    CPEncodingTypeNSString,
    CPEncodingTypeNSMutableString,
    CPEncodingTypeNSValue,
    CPEncodingTypeNSNumber,
    CPEncodingTypeNSDecimalNumber,
    CPEncodingTypeNSData,
    CPEncodingTypeNSMutableData,
    CPEncodingTypeNSDate,
    CPEncodingTypeNSURL,
    CPEncodingTypeNSArray,
    CPEncodingTypeNSMutableArray,
    CPEncodingTypeNSDictionary,
    CPEncodingTypeNSMutableDictionary,
    CPEncodingTypeNSSet,
    CPEncodingTypeNSMutableSet,
};

@interface CPModelMeta : NSObject{
    @package
    CPClassInfo *_clsInfo;
    NSDictionary *_mapper;
    NSArray *_allPropertyMetas;
    NSUInteger _keyMappedCount;
    CPEncodingNSType _nsType;
}

+ (instancetype)metaWithClass:(Class)cls;

@end

@interface CPModelPropertyMeta : NSObject{
    @package
    NSString *_name;
    CPEncodingType _type;
    CPEncodingNSType _nsType;
    BOOL _isCNumber;
    Class _cls;
    Class _genericCls;
    SEL _getter;
    SEL _setter;
    BOOL _isKVCCompatible;
    NSString *_mappedToKey;
    CPClassPropertyInfo *_info;
}

+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)generic;

@end

可以看到這里有兩個(gè)類仿耽,姑且叫做 CPModelPropertyMetaCPModelMeta 以及一個(gè)枚舉合冀,這個(gè)枚舉表示一個(gè)NS的類型,因?yàn)樵谏弦粋€(gè)枚舉當(dāng)中项贺,我們對(duì)于對(duì)象只定義了 CPEncodingTypeObject 這一個(gè)類型君躺,沒法區(qū)分它到底是 NSString 還是別的,所以這里要細(xì)化一下开缎,類型判斷清楚很重要棕叫,如果不把這部分做好,那么在JSON轉(zhuǎn)換的時(shí)候奕删,類型上出錯(cuò)就直接蹦了俺泣。

先來(lái)看一下 CPModelPropertyMeta 。(在 YYModel 中完残,這兩個(gè)類其實(shí)是和一個(gè)叫做NSObject+CPModel的擴(kuò)展放在一起的伏钠,但是我強(qiáng)制把它們拆出來(lái)了,為了看起來(lái)清楚谨设,所以我把 @package 的成員變量都寫到了 interface 里面熟掂,這么做是不合理的,但這里為了清晰和學(xué)習(xí)起見扎拣,所以我亂來(lái)了赴肚。)這個(gè)類中多了幾個(gè)成員變量,我就說(shuō)幾個(gè)看起來(lái)不那么清楚的成員變量二蓝。

_isCNumber 這里變量表示是不是一個(gè)C語(yǔ)言的類型誉券,比如int這樣的。

_genericCls這個(gè)變量在精簡(jiǎn)版里沒用到刊愚,我只是放在這里踊跟,YYModel 可以給容器型的屬性轉(zhuǎn)換,具體可以看YY大神的文檔百拓。

_isKVCCompatible 能不能支持 KVC

_mappedToKey 要映射的 key琴锭,把 JSON 轉(zhuǎn)成 Model 的時(shí)會(huì)根據(jù)這個(gè) key 把相同字段的 JSON 值賦值給這個(gè) Property。

為了判斷 NS 的類型和是否是 C 類型衙传,在 .m 里有兩個(gè)函數(shù)

#define force_inline __inline__ __attribute__((always_inline))

static force_inline CPEncodingNSType CPClassGetNSType(Class cls) {
    if (!cls) return CPEncodingTypeNSUnknown;
    if ([cls isSubclassOfClass:[NSMutableString class]]) return CPEncodingTypeNSMutableString;
    if ([cls isSubclassOfClass:[NSString class]]) return CPEncodingTypeNSString;
    if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return CPEncodingTypeNSDecimalNumber;
    if ([cls isSubclassOfClass:[NSNumber class]]) return CPEncodingTypeNSNumber;
    if ([cls isSubclassOfClass:[NSValue class]]) return CPEncodingTypeNSValue;
    if ([cls isSubclassOfClass:[NSMutableData class]]) return CPEncodingTypeNSMutableData;
    if ([cls isSubclassOfClass:[NSData class]]) return CPEncodingTypeNSData;
    if ([cls isSubclassOfClass:[NSDate class]]) return CPEncodingTypeNSDate;
    if ([cls isSubclassOfClass:[NSURL class]]) return CPEncodingTypeNSURL;
    if ([cls isSubclassOfClass:[NSMutableArray class]]) return CPEncodingTypeNSMutableArray;
    if ([cls isSubclassOfClass:[NSArray class]]) return CPEncodingTypeNSArray;
    if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return CPEncodingTypeNSMutableDictionary;
    if ([cls isSubclassOfClass:[NSDictionary class]]) return CPEncodingTypeNSDictionary;
    if ([cls isSubclassOfClass:[NSMutableSet class]]) return CPEncodingTypeNSMutableSet;
    if ([cls isSubclassOfClass:[NSSet class]]) return CPEncodingTypeNSSet;
    return CPEncodingTypeNSUnknown;
}

static force_inline BOOL CPEncodingTypeIsCNumber(CPEncodingType type) {
    switch (type & CPEncodingTypeMask) {
        case CPEncodingTypeBool:
        case CPEncodingTypeInt8:
        case CPEncodingTypeUInt8:
        case CPEncodingTypeInt16:
        case CPEncodingTypeUInt16:
        case CPEncodingTypeInt32:
        case CPEncodingTypeUInt32:
        case CPEncodingTypeInt64:
        case CPEncodingTypeUInt64:
        case CPEncodingTypeFloat:
        case CPEncodingTypeDouble:
        case CPEncodingTypeLongDouble: return YES;
        default: return NO;
    }
}

這兩個(gè)函數(shù)不用多說(shuō)了决帖,很簡(jiǎn)單,要說(shuō)明一下宏定義 force_inline 所有標(biāo)記了 force_inline 的函數(shù)叫做內(nèi)聯(lián)函數(shù)蓖捶,在調(diào)用的時(shí)候都不是一般的調(diào)用地回,而是在編譯的時(shí)候就已經(jīng)整個(gè)丟進(jìn)了調(diào)用這個(gè)函數(shù)的方法或函數(shù)里去了,這和平時(shí)定義一個(gè)宏一樣,你在哪里使用到了這個(gè)宏刻像,那么在編譯的時(shí)候編譯器就會(huì)把你使用這個(gè)宏的地方替換成宏的值畅买。為什么要這么做呢?因?yàn)樾氏杆{(diào)用一個(gè)函數(shù)也是有開銷的谷羞,調(diào)用一個(gè)函數(shù)有壓棧彈棧等操作。如果你的函數(shù)很小溜徙,你這么一弄就免去了這些操作湃缎。

然后看一下CPModelPropertyMeta的初始化方法

+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)generic{
    CPModelPropertyMeta *meta = [self new];
    meta->_name = propertyInfo.name;
    meta->_type = propertyInfo.type;
    meta->_info = propertyInfo;
    meta->_genericCls = generic;
    
    if ((meta->_type & CPEncodingTypeMask) == CPEncodingTypeObject) {
        meta->_nsType = CPClassGetNSType(propertyInfo.cls);
    } else {
        meta->_isCNumber = CPEncodingTypeIsCNumber(meta->_type);
    }
    
    meta->_cls = propertyInfo.cls;
    
    if (propertyInfo.getter) {
        if ([clsInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    
    if (propertyInfo.setter) {
        if ([clsInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
            meta->_setter = propertyInfo.setter;
        }
    }
    
    if (meta->_setter && meta->_getter) {
        switch (meta->_type & CPEncodingTypeMask) {
            case CPEncodingTypeBool:
            case CPEncodingTypeInt8:
            case CPEncodingTypeUInt8:
            case CPEncodingTypeInt16:
            case CPEncodingTypeUInt16:
            case CPEncodingTypeInt32:
            case CPEncodingTypeUInt32:
            case CPEncodingTypeInt64:
            case CPEncodingTypeUInt64:
            case CPEncodingTypeFloat:
            case CPEncodingTypeDouble:
            case CPEncodingTypeObject:
            case CPEncodingTypeClass:
            case CPEncodingTypeBlock:
            case CPEncodingTypeStruct:
            case CPEncodingTypeUnion: {
                meta->_isKVCCompatible = YES;
            } break;
            default: break;
        }
    }
    
    return meta;
}

判斷一下是否是 object 的類型,然后拿到具體的 NS 類型蠢壹,或者判斷一下是不是 C 類型嗓违,然后拿到 getter setter 最后判斷一下能不能 KVC。

CPModelPropertyMeta

這個(gè)類主要是生成一個(gè)映射表图贸,這個(gè)映射表就是 _mapper 這個(gè)變量蹂季,這個(gè)類也需要被緩存起來(lái),套路和上面講到的緩存套路一樣

+ (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);
    CPModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    
    if (!meta || meta->_clsInfo.needUpdate) {
        meta = [[CPModelMeta 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;
}

緩存沒命中就調(diào)用 initWithClass: 來(lái)進(jìn)行初始化

- (instancetype)initWithClass:(Class)cls{
    if (!cls) {
        return nil;
    }
    
    self = [super init];
    if (self) {
        CPClassInfo *clsInfo = [CPClassInfo classInfoWithClass:cls];
        NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
        CPClassInfo *curClsInfo = clsInfo;
        //連同當(dāng)前的類和其父類的屬性一起放入allPropertyMetas數(shù)組疏日,(NSObject和NSProxy是沒有父類的)
        while (curClsInfo && curClsInfo.superClass != nil) {
            for (CPClassPropertyInfo *propertyInfo in curClsInfo.propertyInfos.allValues) {
                if (!propertyInfo.name)continue;
                CPModelPropertyMeta *meta = [CPModelPropertyMeta modelWithClassInfo:clsInfo propretyInfo:propertyInfo generic:nil];
                if (!meta || !meta->_name)continue;
                if (!meta->_setter || !meta->_getter)continue;
                if (allPropertyMetas[meta->_name])continue;
                allPropertyMetas[meta->_name] = meta;
            }
            curClsInfo = clsInfo.superClassInfo;
        }
        
        if (allPropertyMetas.count) {
            _allPropertyMetas = allPropertyMetas.allValues.copy;
        }
        
        NSMutableDictionary *mapper = [NSMutableDictionary new];
        
        [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *  _Nonnull name, CPModelPropertyMeta *  _Nonnull meta, BOOL * _Nonnull stop) {
            meta->_mappedToKey = name;
            mapper[name] = meta;
        }];
        
        if (mapper.count) _mapper = mapper;
        _clsInfo = clsInfo;
        _keyMappedCount = _allPropertyMetas.count;
        _nsType = CPClassGetNSType(cls);
        
    }
    return self;
}

CPClassInfo 里所有的 propertyInfo 遍歷出來(lái)偿洁,實(shí)例化成一個(gè) CPModelPropertyMeta ,還順便把 CPClassInfo 父類的所有 propertyInfo 也拿出來(lái)制恍,這樣一來(lái)父能,你的 Model 即便有一個(gè)父類也能把父類的 Property 賦值神凑。

然后生成一個(gè)映射表净神,就基本完成了初始化工作了,這張映射表是關(guān)鍵溉委,等一下所有的 JSON 的轉(zhuǎn)換都依賴這一張表鹃唯。

從 JSON 到 Model 的轉(zhuǎn)換

現(xiàn)在進(jìn)入正餐環(huán)節(jié),我們剛才已經(jīng)把所有的準(zhǔn)備工作完成了瓣喊,現(xiàn)在要開始正式的完成從 JSON 到 Model 的轉(zhuǎn)換了坡慌。

首先,先建一個(gè) Category藻三,取名 CPModel洪橘,因?yàn)槲覀冎煌瓿烧麄€(gè) YYMode 的一個(gè)主要功能,所以我們只給出一個(gè)接口就行了棵帽,所以頭文件很簡(jiǎn)單熄求。

#import <Foundation/Foundation.h>

@interface NSObject (CPModel)

+ (instancetype)modelWithJSON:(id)json;

@end

使用者只需要調(diào)用 + modelWithJSON: 即可完成轉(zhuǎn)換的操作。

現(xiàn)在看看這個(gè)方法要怎么實(shí)現(xiàn):

+ (instancetype)modelWithJSON:(id)json {
    NSDictionary *dic = [self _cp_dictionaryWithJSON:json];
    if (!dic || dic == (id)kCFNull) {
        return nil;
    }
    if (![dic isKindOfClass:[NSDictionary class]]) {
        return nil;
    }
    
    Class cls = [self class];
    
    NSObject *one = [cls new];
    if ([one modelSetWithDictionary:dic]) {
        return one;
    }
    return nil;
}

首先先把 JSON 轉(zhuǎn)換成 NSDictionary 逗概,然后得到該 Model 的 Class 去實(shí)例化這個(gè) Model弟晚,接著調(diào)用一個(gè)叫做- modelSetWithDictionary: 的方法。

把 JSON 轉(zhuǎn)換成 NSDictionary 的方法很簡(jiǎn)單

+ (NSDictionary *)_cp_dictionaryWithJSON:(id)json{
    if (!json || json == (id)kCFNull) {
        return nil;
    }
    NSDictionary *dic = nil;
    NSData *data = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    }else if ([json isKindOfClass:[NSString class]]) {
        data = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    }else if ([json isKindOfClass:[NSData class]]) {
        data = json;
    }
    if (data) {
        dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
        if (![dic isKindOfClass:[NSDictionary class]]) {
            dic = nil;
        }
    }
    return dic;
}

然后看一下 - modelSetWithDictionary:

- (BOOL)modelSetWithDictionary:(NSDictionary *)dic{
    if (!dic || dic == (id)kCFNull) {
        return NO;
    }
    if (![dic isKindOfClass:[NSDictionary class]]) {
        return NO;
    }
    CPModelMeta *meta = [CPModelMeta metaWithClass:object_getClass(self)]; //①
    if (meta->_keyMappedCount == 0) {
        return NO;
    }
    
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(meta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    if (meta->_keyMappedCount >= dic.count) {
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
    }
    return YES;
}

這里有一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體用來(lái)存儲(chǔ) model(因?yàn)槭墙o這個(gè)Model 里的 Property 賦值)卿城、modelMeta(剛才也看到了枚钓,這里存放了映射表)、dictionary(這是由 JSON 轉(zhuǎn)換過來(lái)的)瑟押,這個(gè)結(jié)構(gòu)體的定義如下:

typedef struct {
    void *modelMeta;
    void *model;
    void *dictionary;
} ModelSetContext;

然后在- modelSetWithDictionary:有這么一行代碼

CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);

這個(gè)代碼的作用是搀捷,把一對(duì) Key - Value 拿出來(lái),然后調(diào)用你傳進(jìn)去的函數(shù)ModelSetWithDictionaryFunction()多望,你有多少對(duì)Key - Value指煎,它就會(huì)調(diào)用多少次這個(gè)函數(shù),相當(dāng)于便利所有的Key - Value便斥,為什么要這樣做至壤,而不用一個(gè)循環(huán)呢?在作者的博客里有這么一段

遍歷容器類時(shí)枢纠,選擇更高效的方法

相對(duì)于 Foundation 的方法來(lái)說(shuō)像街,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法來(lái)遍歷容器類能帶來(lái)不少性能提升晋渺,但代碼寫起來(lái)會(huì)非常麻煩镰绎。

然后我們來(lái)看一下ModelSetWithDictionaryFunction()的實(shí)現(xiàn)

static void ModelSetWithDictionaryFunction(const void *key, const void *value, void *context) {
    ModelSetContext *ctx = context;
    __unsafe_unretained CPModelMeta *modelMeta = (__bridge CPModelMeta *)(ctx->modelMeta);
    __unsafe_unretained CPModelPropertyMeta *propertyMeta = [modelMeta->_mapper objectForKey:(__bridge id)(key)];
    __unsafe_unretained id model = (__bridge id)(ctx->model);
    if (propertyMeta->_setter) {
        ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)value, propertyMeta);
    }
}

為什么在變量前都加了__unsafe_unretained,作者也說(shuō)了

避免多余的內(nèi)存管理方法

在 ARC 條件下木西,默認(rèn)聲明的對(duì)象是 __strong 類型的畴栖,賦值時(shí)有可能會(huì)產(chǎn)生 retain/release 調(diào)用,如果一個(gè)變量在其生命周期內(nèi)不會(huì)被釋放八千,則使用 __unsafe_unretained 會(huì)節(jié)省很大的開銷吗讶。

訪問具有 __weak 屬性的變量時(shí),實(shí)際上會(huì)調(diào)用 objc_loadWeak() 和 objc_storeWeak() 來(lái)完成恋捆,這也會(huì)帶來(lái)很大的開銷照皆,所以要避免使用 __weak 屬性。

繼續(xù)沸停,根據(jù) key(這個(gè) key 就是 JSON 里的字段膜毁,應(yīng)該和你 Model 定義的 Property 名相同,否則就匹配不了愤钾,在 YYMode 中有一個(gè)自定義映射表的支持瘟滨,我把它去掉了,有興趣的可以下載 YYMode 的源碼看一下) 取出映射表里的 propertyMeta∧馨洌現(xiàn)在我們有了要轉(zhuǎn)換的 model 對(duì)象杂瘸,和一個(gè)和 JSON 里字段對(duì)應(yīng)的 propertyMeta 對(duì)象,已經(jīng)該 JSON 字段的值劲装,現(xiàn)在要賦值的條件全部具備了胧沫,我們只需要調(diào)用propertyMeta中的setter方法昌简,然后把值傳進(jìn)去就完成了,這部分的工作由 ModelSetValueForProperty()函數(shù)完成绒怨,這個(gè)函數(shù)里有大量的類型判斷纯赎,為了簡(jiǎn)單起見,我就判斷了NSString NSNumber 和普通C語(yǔ)言類型南蹂,代碼如下:

static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained CPModelPropertyMeta *meta) {
    if (meta->_isCNumber) {
        NSNumber *num = CPNSNumberCreateFromID(value);
        ModelSetNumberToProperty(model, num, meta);
        if (num) [num class];
    } else if (meta->_nsType) {
        if (value == (id)kCFNull) {
            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
        } else {
            switch (meta->_nsType) {
                case CPEncodingTypeNSString:
                case CPEncodingTypeNSMutableString: {
                    if ([value isKindOfClass:[NSString class]]) {
                        if (meta->_nsType == CPEncodingTypeNSString) {
                            ((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);
                        }
                    } else if ([value isKindOfClass:[NSNumber class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == CPEncodingTypeNSString) ?
                                                                       ((NSNumber *)value).stringValue :
                                                                       ((NSNumber *)value).stringValue.mutableCopy);
                    } else if ([value isKindOfClass:[NSData class]]) {
                        NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
                    } else if ([value isKindOfClass:[NSURL class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == CPEncodingTypeNSString) ?
                                                                       ((NSURL *)value).absoluteString :
                                                                       ((NSURL *)value).absoluteString.mutableCopy);
                    } else if ([value isKindOfClass:[NSAttributedString class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                       meta->_setter,
                                                                       (meta->_nsType == CPEncodingTypeNSString) ?
                                                                       ((NSAttributedString *)value).string :
                                                                       ((NSAttributedString *)value).string.mutableCopy);
                    }
                } break;
                case CPEncodingTypeNSNumber:{
                    if ([value isKindOfClass:[NSNumber class]]) {
                        if (meta->_nsType == CPEncodingTypeNSNumber) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta->_setter,value);
                        }
                    }
                } break;
                    
                default: break;
            }
        }
    }
}

關(guān)于 objc_msgSend() 我們隨便拿一行例子來(lái)舉例犬金,比如這個(gè):

((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);

這是一個(gè)可以調(diào)用者決定返回值和參數(shù)的函數(shù),一般的函數(shù)是做不到的六剥,默認(rèn)情況下這個(gè)函數(shù)是長(zhǎng)這樣

objc_msgSend(id, SEL)

id 是指調(diào)用某一個(gè)方法的對(duì)象晚顷,在這里這個(gè)對(duì)象就是你的 Model

SEL 是指你這個(gè)對(duì)象要調(diào)用的方法是什么,在這里這個(gè)方法就是 setter方法

然而疗疟,setter 方法是有參數(shù)的该默,這個(gè)參數(shù)怎么傳進(jìn)去?這就需要強(qiáng)制類型轉(zhuǎn)換了策彤,我們把這個(gè)函數(shù)強(qiáng)制轉(zhuǎn)換成這個(gè)模樣:

((void (*)(id, SEL, id))(void *) objc_msgSend)

這樣代表這個(gè)函數(shù)是一個(gè)沒有返回值栓袖,并且有3個(gè)參數(shù)的函數(shù),分別是 id SEL id店诗,前面兩個(gè)參數(shù)之前講過了裹刮,第三個(gè)參數(shù)就是你要調(diào)用的這個(gè) setter 方法需要的參數(shù),所以經(jīng)過強(qiáng)制類型轉(zhuǎn)換之后的變異版就成了一開始的那種樣子庞瘸。

其余的都沒有什么好講的了捧弃,都很簡(jiǎn)單,都是一些煩人的類型判斷擦囊,只要仔細(xì)一點(diǎn)一行行看就能看懂了违霞。

全部搞定以后,和原版的 YYModel 一樣霜第,你可以這么來(lái)測(cè)試

CPTestModel *model = [CPTestModel modelWithJSON:@"{\"name\": \"Harry Potter\",\"index\": 512,\"number\": 10,\"num\": 100}"];

結(jié)尾

如果你自己親自動(dòng)手寫完了這個(gè)精簡(jiǎn)版的 YYMode 葛家,你再去看完整版的會(huì)容易很多户辞,我寫的這篇文章是把我從讀 YYModel 源碼中學(xué)到的一些有用的東西分享給大家泌类,如有什么寫錯(cuò)的地方,歡迎指正底燎。

完整代碼

點(diǎn)擊這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刃榨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子双仍,更是在濱河造成了極大的恐慌枢希,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朱沃,死亡現(xiàn)場(chǎng)離奇詭異苞轿,居然都是意外死亡茅诱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門搬卒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瑟俭,“玉大人,你說(shuō)我怎么就攤上這事契邀“诩模” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵坯门,是天一觀的道長(zhǎng)微饥。 經(jīng)常有香客問我,道長(zhǎng)古戴,這世上最難降的妖魔是什么欠橘? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮现恼,結(jié)果婚禮上简软,老公的妹妹穿的比我還像新娘。我一直安慰自己述暂,他們只是感情好痹升,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畦韭,像睡著了一般疼蛾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艺配,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天察郁,我揣著相機(jī)與錄音,去河邊找鬼转唉。 笑死皮钠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赠法。 我是一名探鬼主播麦轰,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砖织!你這毒婦竟也來(lái)了款侵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侧纯,失蹤者是張志新(化名)和其女友劉穎新锈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眶熬,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妹笆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年块请,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拳缠。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡负乡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脊凰,到底是詐尸還是另有隱情抖棘,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布狸涌,位于F島的核電站切省,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帕胆。R本人自食惡果不足惜朝捆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懒豹。 院中可真熱鬧芙盘,春花似錦、人聲如沸脸秽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)记餐。三九已至驮樊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間片酝,已是汗流浹背囚衔。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雕沿,地道東北人练湿。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像审轮,于是被迫代替她去往敵國(guó)和親肥哎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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