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
此次分析,将会先看一下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));,所以会有两个参数。