读完这篇文章你可以自己写一个 YYModel 这样的神器,这篇文章类似一个源码解析,但不同的是,它不光光是解析,更是实战,因为我觉得学习一个东西必须要自己写一遍才算是真的学了一遍,否则即便是读完了源码印象还是不会太深刻,so,开始吧。
注:为了简单起见,我的例子只是实现了一个精简的版本,YYModel 有很多功能,我这里就实现了一个核心的功能,JSON -> Model
。
注:文章的最后有完整的代码
从JSON映射到Model的原理
想一下平时我们是怎么使用类似这样子的库的,当我们有一个JSON的时候,我们把所有JSON的字段(比如name、page)全部写成对应的类中的属性。然后库会自动把你JSON对应字段的值赋值到这些对应的属性里去。属性我们用 @property
来定义,就意味着编译器会帮你生成对应的get
方法,我们使用的 set
.
其实也是在调用get
方法来实现赋值的。在 Objective-C 中有一个著名的函数 set
objc_msgSend(...)
我们所有的类似 [obj method]
的方法调用(发送消息)都会被转换成 objc_msgSend(...)
的方式来调用。(具体这个函数怎么用后面再说)
所以对于一个库来说,要调用你这个 Model 的 set
方法,用 objc_msgSend(...)
会容易的多,所以JSON映射到Model的原理其实就是调用这个函数而已。
所以整个流程就是,你给我一个 Model 类,我会用 runtime 提供的各种函数来拿到你所有的属性和对应的get
,判断完相应的类型以后,调用objc_msgSend(…)。说起来真的非常简单,做起来就有点麻烦…set
前期的准备工作
为了后面的方便,我们需要把一些关键的东西封装起来,我们需要单独封装 ivar
property
method
,也就是实例变量、属性、方法,但事实上我们的这个精简版的YYModel并不需要 method
ivar
的封装,为了保证完整性,我还是打算写下来。
封装 ivar
先来封装 ivar
,看一下头文件 CPClassIvarInfo.h
(YYModel只有4个文件,两个 .h
两个 .m
我为了让代码看起来更清楚,所以我自己在重写 YYModel 的时候把所有可以拆出来的类都分别拆成了一对.h .m
)并把前缀改成了 CP 意思是 copy。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#import #import #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
代表一个实例变量,你所有和实例变量有关的操作都必须要把 Ivar
传进去,等一下就能看到。
name
是这个实例变量的变量名
typeEncoding
是对类型的编码,具体可以看这里 对于不同的类型就会有对应的编码,比如 int
就会变编码成 i
,可以用 @encode(int)
这样的操作来看一个类型的编码。
type
是一个自定义的枚举,它描述了 YYMode 规定的类型。
一个强大的枚举
然后重新再创建一个文件(CPCommon),作为一个公共的文件 CPEncodingType
这个枚举就写在这里。
我们要创建的这个枚举需要一口气表示三种不同的类型,一种用于普通的类型上(int double object
),一种用来表示关键词(const
),一种表示 Property 的属性(Nonatomic
weak
retain
)。
我们可以用位运算符来搞定这三种类型,用8位的枚举值来表示第一种,16位的表示第二种,24位的表示第三种,然后为了区别这三种类型都属于多少位的,我们可以分别搞三个 mask ,做一个该类型和某一个 mask 的与(&)的操作就可以知道这个类型是具体是哪一个类型了,例子在后面。
这个枚举我们可以这样定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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 |
比如有一个类型是这样的
1 |
CPEncodingType type = CPEncodingTypeDouble; |
假设我们并不知道它是 CPEncodingTypeDouble
类型,那我们要怎么样才能知道它是什么类型呢?只要这样:
1 |
NSLog(@"%lu",type & CPEncodingTypeMask); |
输出: 12
在枚举的定义中
1 |
CPEncodingTypeDouble = 12, |
假设这个枚举值有很多种混在一起
1 2 |
CPEncodingType type = CPEncodingTypeDouble | CPEncodingTypePropertyRetain; NSLog(@"%lu",type & CPEncodingTypePropertyMask); //输出 262144 (1 |
可能有人知道这种神奇的用法,但在我读YYModel之前我没用过这种方法(技术比较菜)。
然后还有一个函数,这个函数可以把类型编码(Type Encoding)转换成刚才的枚举值,很简单却很长的一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
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 |