走进 YYModel

作者: 水落斜阳 | 来源:发表于2018-09-20 17:11 被阅读168次

    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 <NSObject>:接口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 <Foundation/Foundation.h>
    
    #if __has_include(<YYModel/YYModel.h>)
    FOUNDATION_EXPORT double YYModelVersionNumber;
    FOUNDATION_EXPORT const unsigned char YYModelVersionString[];
    #import <YYModel/NSObject+YYModel.h>
    #import <YYModel/YYClassInfo.h>
    #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<NSString *> *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

        本文链接:https://www.haomeiwen.com/subject/xwmxvxtx.html