走进 YYModel

YYModel使用

如果有这样一组json数据:

{
"number":"13612345678", 
"name":"Germany",
 "age": 49
}

那我们会去建立相应的Object对象

@interface TestObject : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;
@property (nonatomic, assign) NSInteger age;
@end

调用

// 从 JSON 转为 Model:
TestObject *testObject = [TestObject yy_modelWithJSON:json];

//从 Model 转为 JSON:
NSDictionary *json = [testObject yy_modelToJSONObject];
就可以进行类型的转化。

显然,相较于JSONModel每个model类都必须继承于JSONModel类的作法,YYModel更佳方便和快捷

整体结构

YYModel本身的目录结构十分精简:

包括:

文件YYModel.h:导入YYModel头文件
文件NSObject+YYModel:YYModel主体Category
文件YYClassInfo:Class解析类


文件YYClassInfo中包含:

@interface YYClassIvarInfo : NSObject:对Class的Ivar进行解析与构造
@interface YYClassMethodInfo : NSObject:对Class的Method进行解析与构造
@interface YYClassPropertyInfo : NSObject:对Class的Property进行解析与构造
@interface YYClassInfo : NSObject:通过以上三种解析,对Class进行解析与构造

文件NSObject+YYModel中包含:

@interface _YYModelPropertyMeta : NSObject:对Model的property进行解析与构造(.m中的private类)

@interface _YYModelMeta : NSObject:对Model进行解析与构造(.m中的private类)

@interface NSObject (YYModel):NSObject的YYModel Category

@interface NSArray (YYModel):NSArray的YYModel Category

@interface NSDictionary (YYModel):NSDictionary的YYModel Category

@protocol YYModel :接口YYModel

此次分析,将会先看一下yy_modelWithJSON方法的调用,讲一下大体代码思路,然后就分别分析YYModel.h,YYClassInfo,

NSObject+YYModel源代码,相当于从下而上进行分析。

大体思路

//先转化json对象到dictionary,再调用yy_modelWithDictionary
+ (instancetype)yy_modelWithJSON:(id)json {
    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
    return [self yy_modelWithDictionary:dic];
}

其中_yy_dictionaryWithJSON就是将id的json对象转成dictionary

//解析model属性并附值
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
    if (!dictionary || dictionary == (id)kCFNull) return nil;
    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
    
    Class cls = [self class];
    //解析class得到modelmeta对象
    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
    //本地class类型映射
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    NSObject *one = [cls new];
    //附值函数
    if ([one yy_modelSetWithDictionary:dictionary]) return one;
    return nil;
}

大致的思路就是先通过_yy_dictionaryWithJSON将json对象转成dictionary,然后调用yy_modelWithDictionary,解析获得解析出来的_YYModelMeta对象(有缓存),判断是否有本地的class类型映射,最后再通过yy_modelSetWithDictionary进行附值,返回model对象。

YYModel.h

YYModel.h本身只是个倒入项目的头文件,代码如下:

#import 

#if __has_include()
FOUNDATION_EXPORT double YYModelVersionNumber;
FOUNDATION_EXPORT const unsigned char YYModelVersionString[];
#import 
#import 
#else
#import "NSObject+YYModel.h"
#import "YYClassInfo.h"
#endif

头文件并不难理解,先试判断是否包含__has_include,然后再引入正确的文件。

include / #import 语句有两种方式包含头文件,分别是使用双引号" "与左右尖括号< >。其区别是(对于不是使用完全文件路径名的)头文件的搜索顺序不同

使用双引号" "的头文件的搜索顺序:

包含该#include语句的源文件所在目录;
包含该#include语句的源文件的已经打开的头文件的逆序;
编译选项-I所指定的目录
环境变量include所定义的目录
使用左右尖括号< >的头文件的搜索顺序:

编译选项-I所指定的目录
环境变量include所定义的目录

主体分层

YYClassInfo主要分为以下几部分:

typedef NS_OPTIONS(NSUInteger, YYEncodingType)与
YYEncodingType YYEncodingGetType(const char *typeEncoding);方法

@interface YYClassIvarInfo : NSObject

@interface YYClassMethodInfo : NSObject

@interface YYClassPropertyInfo : NSObject

@interface YYClassInfo : NSObject

YYClassInfo源代码

(1).typedef NS_OPTIONS(NSUInteger, YYEncodingType)与YYEncodingType YYEncodingGetType(const char *typeEncoding)方法

在YYClassInfo.h中,先定义了一个NS_OPTIONS:

typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
    //0~8位:变量类型
    YYEncodingTypeMask       = 0xFF, ///< mask of type value
    YYEncodingTypeUnknown    = 0, ///< unknown
    YYEncodingTypeVoid       = 1, ///< void
    YYEncodingTypeBool       = 2, ///< bool
    YYEncodingTypeInt8       = 3, ///< char / BOOL
    YYEncodingTypeUInt8      = 4, ///< unsigned char
    YYEncodingTypeInt16      = 5, ///< short
    YYEncodingTypeUInt16     = 6, ///< unsigned short
    YYEncodingTypeInt32      = 7, ///< int
    YYEncodingTypeUInt32     = 8, ///< unsigned int
    YYEncodingTypeInt64      = 9, ///< long long
    YYEncodingTypeUInt64     = 10, ///< unsigned long long
    YYEncodingTypeFloat      = 11, ///< float
    YYEncodingTypeDouble     = 12, ///< double
    YYEncodingTypeLongDouble = 13, ///< long double
    YYEncodingTypeObject     = 14, ///< id
    YYEncodingTypeClass      = 15, ///< Class
    YYEncodingTypeSEL        = 16, ///< SEL
    YYEncodingTypeBlock      = 17, ///< block
    YYEncodingTypePointer    = 18, ///< void*
    YYEncodingTypeStruct     = 19, ///< struct
    YYEncodingTypeUnion      = 20, ///< union
    YYEncodingTypeCString    = 21, ///< char*
    YYEncodingTypeCArray     = 22, ///< char[10] (for example)

    //8~16位:方法类型
    YYEncodingTypeQualifierMask   = 0xFF00,   ///< mask of qualifier
    YYEncodingTypeQualifierConst  = 1 << 8,  ///< const
    YYEncodingTypeQualifierIn     = 1 << 9,  ///< in
    YYEncodingTypeQualifierInout  = 1 << 10, ///< inout
    YYEncodingTypeQualifierOut    = 1 << 11, ///< out
    YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy
    YYEncodingTypeQualifierByref  = 1 << 13, ///< byref
    YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway

    //16~24位:property修饰类型
    YYEncodingTypePropertyMask         = 0xFF0000, ///< mask of property
    YYEncodingTypePropertyReadonly     = 1 << 16, ///< readonly
    YYEncodingTypePropertyCopy         = 1 << 17, ///< copy
    YYEncodingTypePropertyRetain       = 1 << 18, ///< retain
    YYEncodingTypePropertyNonatomic    = 1 << 19, ///< nonatomic
    YYEncodingTypePropertyWeak         = 1 << 20, ///< weak
    YYEncodingTypePropertyCustomGetter = 1 << 21, ///< getter=
    YYEncodingTypePropertyCustomSetter = 1 << 22, ///< setter=
    YYEncodingTypePropertyDynamic      = 1 << 23, ///< @dynamic
};

该NS_OPTIONS主要定义了3个大类encode type:

YYEncodingTypeMask:
变量类型,因为类型只会有一种,所以就用数字站位

YYEncodingTypeQualifierMask:
方法中的参数变量修饰符,理论上只有解析Method的参数才能解析到

YYEncodingTypePropertyMask
property修饰符类型

这边对于YYEncodingTypeQualifierMask和YYEncodingTypePropertyMask因为存在多种可能的情况,使用了位移(<<)的方式,通过与(&)YYEncodingTypeQualifierMask和YYEncodingTypePropertyMask的方式,判断是否包含某个值。

获取Ivar类型的函数如下:

//解析Ivar的type encode string
YYEncodingType YYEncodingGetType(const char *typeEncoding) {
    char *type = (char *)typeEncoding;
    if (!type) return YYEncodingTypeUnknown;
    size_t len = strlen(type);
    if (len == 0) return YYEncodingTypeUnknown;

    YYEncodingType qualifier = 0;
    bool prefix = true;
    while (prefix) {
        //方法参数Ivar中的解析,理论上解析不到该类参数
        switch (*type) {
            case 'r': {
                qualifier |= YYEncodingTypeQualifierConst;
                type++;
            } break;
            case 'n': {
                qualifier |= YYEncodingTypeQualifierIn;
                type++;
            } break;
            case 'N': {
                qualifier |= YYEncodingTypeQualifierInout;
                type++;
            } break;
            case 'o': {
                qualifier |= YYEncodingTypeQualifierOut;
                type++;
            } break;
            case 'O': {
                qualifier |= YYEncodingTypeQualifierBycopy;
                type++;
            } break;
            case 'R': {
                qualifier |= YYEncodingTypeQualifierByref;
                type++;
            } break;
            case 'V': {
                qualifier |= YYEncodingTypeQualifierOneway;
                type++;
            } break;
            default: { prefix = false; } break;
        }
    }

    len = strlen(type);
    if (len == 0) return YYEncodingTypeUnknown | qualifier;

    //返回值类型解析
    switch (*type) {
        case 'v': return YYEncodingTypeVoid | qualifier;
        case 'B': return YYEncodingTypeBool | qualifier;
        case 'c': return YYEncodingTypeInt8 | qualifier;
        case 'C': return YYEncodingTypeUInt8 | qualifier;
        case 's': return YYEncodingTypeInt16 | qualifier;
        case 'S': return YYEncodingTypeUInt16 | qualifier;
        case 'i': return YYEncodingTypeInt32 | qualifier;
        case 'I': return YYEncodingTypeUInt32 | qualifier;
        case 'l': return YYEncodingTypeInt32 | qualifier;
        case 'L': return YYEncodingTypeUInt32 | qualifier;
        case 'q': return YYEncodingTypeInt64 | qualifier;
        case 'Q': return YYEncodingTypeUInt64 | qualifier;
        case 'f': return YYEncodingTypeFloat | qualifier;
        case 'd': return YYEncodingTypeDouble | qualifier;
        case 'D': return YYEncodingTypeLongDouble | qualifier;
        case '#': return YYEncodingTypeClass | qualifier;
        case ':': return YYEncodingTypeSEL | qualifier;
        case '*': return YYEncodingTypeCString | qualifier;
        case '^': return YYEncodingTypePointer | qualifier;
        case '[': return YYEncodingTypeCArray | qualifier;
        case '(': return YYEncodingTypeUnion | qualifier;
        case '{': return YYEncodingTypeStruct | qualifier;
        case '@': {
            if (len == 2 && *(type + 1) == '?')
                return YYEncodingTypeBlock | qualifier;     //OC Block
            else
                return YYEncodingTypeObject | qualifier;    //OC对象
        }
        default: return YYEncodingTypeUnknown | qualifier;
    }
}

该函数也是通过获得的type encode的string,对照着表进行解析,因为是解析Ivar,所以也只包含了YYEncodingTypeMask和YYEncodingTypeQualifierMask。而YYEncodingTypePropertyMask会包含在property的解析中。

####(2).@interface YYClassIvarInfo : NSObject
YYClassIvarInfo类声明:

/**
 Instance variable information.
 */
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct ivar本身指针
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name        ivar名
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset      ivar偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding   ivar encode string
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type        ivar encode解析值

/**
 Creates and returns an ivar info object.

 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithIvar:(Ivar)ivar;
@end
initWithIvar方法实现:

- (instancetype)initWithIvar:(Ivar)ivar {
    if (!ivar) return nil;
    self = [super init];
    _ivar = ivar;
    const char *name = ivar_getName(ivar);      //获取ivar名
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    _offset = ivar_getOffset(ivar);             //获取便宜量
    const char *typeEncoding = ivar_getTypeEncoding(ivar);  //获取类型encode string
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        _type = YYEncodingGetType(typeEncoding);    //类型解析
    }
    return self;
}

YYClassIvarInfo本身就是对系统Ivar的一层封装,并进行了一次类型的解析。

实例

用YYModel测试用例来进行观察:
YYTestNestRepo实现:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo调用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

设置解析断点在解析@property YYTestNestUser *user;的Ivar变量处:


(3).@interface YYClassMethodInfo : NSObject

YYClassMethodInfo类声明:

@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;                  ///< method opaque struct method指针
@property (nonatomic, strong, readonly) NSString *name;                 ///< method name            method名
@property (nonatomic, assign, readonly) SEL sel;                        ///< method's selector      method selector
@property (nonatomic, assign, readonly) IMP imp;                        ///< method's implementation    method implementation
@property (nonatomic, strong, readonly) NSString *typeEncoding;         ///< method's parameter and return types    method的参数和返回类型
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   ///< return value's type    method返回值的encode types
@property (nullable, nonatomic, strong, readonly) NSArray *argumentTypeEncodings; ///< array of arguments' type method参数列表

- (instancetype)initWithMethod:(Method)method;
@end

initWithMethod方法实现:

- (instancetype)initWithMethod:(Method)method {
    if (!method) return nil;
    self = [super init];
    _method = method;
    _sel = method_getName(method);                      //获取方法名,在oc中,方法名就是selector的标志
    _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 *returnType = method_copyReturnType(method);           //获得返回值encode string
    if (returnType) {
        _returnTypeEncoding = [NSString stringWithUTF8String:returnType];
        free(returnType);
    }
    unsigned int argumentCount = method_getNumberOfArguments(method);       //获得方法参数数量
    if (argumentCount > 0) {
        NSMutableArray *argumentTypes = [NSMutableArray new];
        for (unsigned int i = 0; i < argumentCount; i++) {                  //遍历参数
            char *argumentType = method_copyArgumentType(method, i);        //获得该参数的encode string
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;
    }
    return self;
}
实例

用YYModel测试用例来进行观察:
YYTestNestRepo实现:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo调用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

设置解析断点解析user方法:


对于property来说,本质是:Ivar+getter+setter,所以设置了property也会触发initWithMethod解析-(YYTestNestUser *) user;方法,该方法的解析如上图。

这边比较有意思的是,明明user没有参数,怎么method_getNumberOfArguments解析出来2个参数
原因就是方法调用最后都会转成((void (*)(id, SEL))objc_msgSend)((id)m, @selector(user));,所以会有两个参数。

你可能感兴趣的:(走进 YYModel)