手把手帶你擼一個 YYModel 的精簡版

讀完這篇文章你可以自己寫一個 YYModel 這樣的神器,這篇文章類似一個源碼解析,但不同的是,它不光光是解析,更是實戰(zhàn),因為我覺得學(xué)習(xí)一個東西必須要自己寫一遍才算是真的學(xué)了一遍,否則即便是讀完了源碼印象還是不會太深刻,so,開始吧。

注:為了簡單起見,我的例子只是實現(xiàn)了一個精簡的版本,YYModel 有很多功能,我這里就實現(xiàn)了一個核心的功能,JSON -> Model

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

從JSON映射到Model的原理

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

所以對于一個庫來說,要調(diào)用你這個 Model 的 set 方法,用 objc_msgSend(...) 會容易的多,所以JSON映射到Model的原理其實就是調(diào)用這個函數(shù)而已。

所以整個流程就是,你給我一個 Model 類,我會用 runtime 提供的各種函數(shù)來拿到你所有的屬性和對應(yīng)的get``set,判斷完相應(yīng)的類型以后,調(diào)用objc_msgSend(...)。說起來真的非常簡單,做起來就有點麻煩...

前期的準備工作

為了后面的方便,我們需要把一些關(guān)鍵的東西封裝起來,我們需要單獨封裝 ivar property method,也就是實例變量、屬性、方法,但事實上我們的這個精簡版的YYModel并不需要 method ivar 的封裝,為了保證完整性,我還是打算寫下來。

封裝 ivar

先來封裝 ivar,看一下頭文件 CPClassIvarInfo.h(YYModel只有4個文件,兩個 .h 兩個 .m 我為了讓代碼看起來更清楚,所以我自己在重寫 YYModel 的時候把所有可以拆出來的類都分別拆成了一對.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 代表一個實例變量,你所有和實例變量有關(guān)的操作都必須要把 Ivar 傳進去,等一下就能看到。

name 是這個實例變量的變量名

typeEncoding 是對類型的編碼,具體可以看這里 對于不同的類型就會有對應(yīng)的編碼,比如 int 就會變編碼成 i,可以用 @encode(int)這樣的操作來看一個類型的編碼。

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

一個強大的枚舉

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

我們要創(chuàng)建的這個枚舉需要一口氣表示三種不同的類型,一種用于普通的類型上(int double object),一種用來表示關(guān)鍵詞(const),一種表示 Property 的屬性(Nonatomic weak retain)。

我們可以用位運算符來搞定這三種類型,用8位的枚舉值來表示第一種,16位的表示第二種,24位的表示第三種,然后為了區(qū)別這三種類型都屬于多少位的,我們可以分別搞三個 mask ,做一個該類型和某一個 mask 的與(&)的操作就可以知道這個類型是具體是哪一個類型了,例子在后面。

這個枚舉我們可以這樣定義:

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,
};

比如有一個類型是這樣的

CPEncodingType type = CPEncodingTypeDouble;

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

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

輸出: 12

在枚舉的定義中

CPEncodingTypeDouble     = 12,

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

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

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

然后還有一個函數(shù),這個函數(shù)可以把類型編碼(Type Encoding)轉(zhuǎn)換成剛才的枚舉值,很簡單卻很長的一個函數(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;
    }
}

很簡單,不用多講了。

回到 CPClassIvarInfo 剛才我們只給出了頭文件,現(xiàn)在看一下實現(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;
}

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

封裝Method

然后看一下對于 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 是成對出現(xiàn)的,因為 Swift 可以和 Objective-C 混用,但是 Swift 有 Optional 類型,而 Objective-C 沒有這樣的概念,為了和 Swift 保持一致,現(xiàn)在 Objective-C 有了 _Nullable 可空 _Nonnull不可空這樣的關(guān)鍵字,這兩個關(guān)鍵字可以在變量、方法返回值、方法參數(shù)上使用,比如:

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

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

還有另外一對 nullable nonnull,它們可以這樣用

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

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

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

當你一旦在某個地方寫上關(guān)鍵詞 nullable的時候,編譯器就會提出警告,Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified) 然后你就可以加上NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END來表示只有我標記為 nullable 的地方才可空,其余地方都是 nonnull

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

name 很明顯就是方法名了

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

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

returnTypeEncoding 返回值的編碼

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

實現(xià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

這是在精簡版的YYModel中會用到的一個類,這里尤其要注意的是typetypdEncoding兩個屬性,希望讀者能夠仔細調(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){
                    //如果該類型為一個對象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString,目的是為了得到這個類的 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這個函數(shù)得到一個指向一個結(jié)構(gòu)體objc_property_attribute_t的指針,這個結(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;

說是一個指針,其實它是一個結(jié)構(gòu)體數(shù)組,指針指向的其實是這個數(shù)組第一個元素。

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

要說清這個數(shù)組里每一個結(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);

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

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

第一個元素 name = Tvalue = @"NSString",第二個元素 name = &value 沒有值,第三個元素 name = Nvalue 仍然沒有值,第四個元素 name = Vvalue = _name。不信可以運行一下下面的代碼來看看。

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 & 這樣的符號是什么意思,可以打開上面給出的鏈接自己看一下文檔,一看便知。

這樣一來在 switch 分支中,只要匹配到 T 就能得到這個 Property 的類型是什么,這樣就可以得到這個類型的 Type Encoding,并且能夠得到該類的 Class。只要匹配到 V 就能得到這個 Property 實例變量名。

該類全部代碼如下:

- (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){
                            //如果該類型為一個對象 比如 @"NSString" ,截取中間的,結(jié)果為 NSString,目的是為了得到這個類的 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;
}

這樣一來,我們就有了 ivar Method Property 的封裝類。接下來,我們需要一個叫做CPClassInfo的類,來封裝一些類的信息,并且把以上三個類也封裝進去,用來描述整個類。

封裝 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 類型用來描述一個類,你可以使用

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

等方法來取到這個 Class。·注意object_getClass()和其他方式 有些不同具體看這里

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

最后的幾個 NSDictionary 用來存所有的 ivar Method Property。

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

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

先來看一下初始化方法

- (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;
}

你沒看錯,這和頭文件定義的classInfoWithClass:不是一個方法,頭文件定義的那個方法用來緩存,因為實例化這個方法還是有點開銷的,所以沒有必要每一次都去實例化。

這里有一個 _update 方法,剛才說過,如果這個類會在某一個時刻發(fā)生變化,應(yīng)該通知,收到通知后,我們?nèi)?zhí)行一些更新的操作,所以把會發(fā)生變化的一部分代碼單獨拿出來更好,現(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;
}

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

最后一行的 _needUpdate 是一個全局變量,用來標識是否發(fā)生的變化,它被定義在這里,以免暴露給外面。

@implementation CPClassInfo{
    BOOL _needUpdate;
}

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

- (BOOL)needUpdate {
    return _needUpdate;
}

- (void)setNeedUpadte {
    _needUpdate = YES;
}

現(xiàn)在來看一下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;
}

兩個 NSMutableDictionary 都是用來緩存的,并聲明在了靜態(tài)區(qū),并且使用dispatch_once()來確保只會被初始化一次,然后我們需要保證線程安全,因為有可能會在多線程的場景里被用到,所以使用信號量dispatch_semaphore_t來搞定,信號量就像停車這樣的場景一樣,如果發(fā)現(xiàn)車滿了,就等待,一有空位就放行,也就是說,當一個線程要進入臨界區(qū)的時候,必須獲取一個信號量,如果沒有問題就進入臨界區(qū),這時另一個線程進來了,也要獲取,發(fā)現(xiàn)信號量并沒有釋放,就繼續(xù)等待,直到前面一個信號量被釋放后,該線程才準許進入。我們可以使用dispatch_semaphore_wait()來獲取信號量,通過dispatch_semaphore_signal()來釋放信號量。

在這段代碼里,我們首先確保要實例化的這個對象有沒有被緩存,用傳進來的 cls 作為 key,如果緩存命中,那直接取出緩存,然后判斷一下,有沒有更新,如果有更新,調(diào)用_update刷新一遍,返回,否則直接返回。緩存沒有命中的話,還是乖乖的調(diào)用實例化方法,然后緩存起來。

繼續(xù)封裝

CPModelPropertyMeta

先建一個文件,叫做 CPMeta.hCPMeta.m,我們要在這里寫兩個類,一個是對 Property 的再次封裝,一個是對 Class 的再次封裝。

我直接把頭文件代碼全拿出來了:

#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

可以看到這里有兩個類,姑且叫做 CPModelPropertyMetaCPModelMeta 以及一個枚舉,這個枚舉表示一個NS的類型,因為在上一個枚舉當中,我們對于對象只定義了 CPEncodingTypeObject 這一個類型,沒法區(qū)分它到底是 NSString 還是別的,所以這里要細化一下,類型判斷清楚很重要,如果不把這部分做好,那么在JSON轉(zhuǎn)換的時候,類型上出錯就直接蹦了。

先來看一下 CPModelPropertyMeta 。(在 YYModel 中,這兩個類其實是和一個叫做NSObject+CPModel的擴展放在一起的,但是我強制把它們拆出來了,為了看起來清楚,所以我把 @package 的成員變量都寫到了 interface 里面,這么做是不合理的,但這里為了清晰和學(xué)習(xí)起見,所以我亂來了。)這個類中多了幾個成員變量,我就說幾個看起來不那么清楚的成員變量。

_isCNumber 這里變量表示是不是一個C語言的類型,比如int這樣的。

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

_isKVCCompatible 能不能支持 KVC

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

為了判斷 NS 的類型和是否是 C 類型,在 .m 里有兩個函數(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;
    }
}

這兩個函數(shù)不用多說了,很簡單,要說明一下宏定義 force_inline 所有標記了 force_inline 的函數(shù)叫做內(nèi)聯(lián)函數(shù),在調(diào)用的時候都不是一般的調(diào)用,而是在編譯的時候就已經(jīng)整個丟進了調(diào)用這個函數(shù)的方法或函數(shù)里去了,這和平時定義一個宏一樣,你在哪里使用到了這個宏,那么在編譯的時候編譯器就會把你使用這個宏的地方替換成宏的值。為什么要這么做呢?因為效率,調(diào)用一個函數(shù)也是有開銷的,調(diào)用一個函數(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

這個類主要是生成一個映射表,這個映射表就是 _mapper 這個變量,這個類也需要被緩存起來,套路和上面講到的緩存套路一樣

+ (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: 來進行初始化

- (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;
        //連同當前的類和其父類的屬性一起放入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 遍歷出來,實例化成一個 CPModelPropertyMeta ,還順便把 CPClassInfo 父類的所有 propertyInfo 也拿出來,這樣一來,你的 Model 即便有一個父類也能把父類的 Property 賦值。

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

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

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

首先,先建一個 Category,取名 CPModel,因為我們只完成整個 YYMode 的一個主要功能,所以我們只給出一個接口就行了,所以頭文件很簡單。

#import <Foundation/Foundation.h>

@interface NSObject (CPModel)

+ (instancetype)modelWithJSON:(id)json;

@end

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

現(xiàn)在看看這個方法要怎么實現(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 去實例化這個 Model,接著調(diào)用一個叫做- modelSetWithDictionary: 的方法。

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

+ (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;
}

這里有一個結(jié)構(gòu)體,這個結(jié)構(gòu)體用來存儲 model(因為是給這個Model 里的 Property 賦值)、modelMeta(剛才也看到了,這里存放了映射表)、dictionary(這是由 JSON 轉(zhuǎn)換過來的),這個結(jié)構(gòu)體的定義如下:

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

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

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

這個代碼的作用是,把一對 Key - Value 拿出來,然后調(diào)用你傳進去的函數(shù)ModelSetWithDictionaryFunction(),你有多少對Key - Value,它就會調(diào)用多少次這個函數(shù),相當于便利所有的Key - Value,為什么要這樣做,而不用一個循環(huán)呢?在作者的博客里有這么一段

遍歷容器類時,選擇更高效的方法

相對于 Foundation 的方法來說,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法來遍歷容器類能帶來不少性能提升,但代碼寫起來會非常麻煩。

然后我們來看一下ModelSetWithDictionaryFunction()的實現(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,作者也說了

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

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

訪問具有 __weak 屬性的變量時,實際上會調(diào)用 objc_loadWeak() 和 objc_storeWeak() 來完成,這也會帶來很大的開銷,所以要避免使用 __weak 屬性。

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

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() 我們隨便拿一行例子來舉例,比如這個:

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

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

objc_msgSend(id, SEL)

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

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

然而,setter 方法是有參數(shù)的,這個參數(shù)怎么傳進去?這就需要強制類型轉(zhuǎn)換了,我們把這個函數(shù)強制轉(zhuǎn)換成這個模樣:

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

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

其余的都沒有什么好講的了,都很簡單,都是一些煩人的類型判斷,只要仔細一點一行行看就能看懂了。

全部搞定以后,和原版的 YYModel 一樣,你可以這么來測試

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

結(jié)尾

如果你自己親自動手寫完了這個精簡版的 YYMode ,你再去看完整版的會容易很多,我寫的這篇文章是把我從讀 YYModel 源碼中學(xué)到的一些有用的東西分享給大家,如有什么寫錯的地方,歡迎指正。

完整代碼

點擊這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

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