YYModel是iOS开发中经常使用到的JSON模型转换库, 注重高性能.
YYModel的源码准备分两部分阅读, 第一部分是YYClassInfo主要是对runtime方法的一些封装, 第二部分是NSObject+YYModel是json转模型的主要实现.
类型编码 (Type Encoding)
当通过runtime获取类中的属性时, 可以通过property_copyAttributeList函数获取属性对应的类型编码, 通过类型编码就可以知道属性的数据类型和属性修饰符等.
关于类型编码中各个符号对应的类型, 参考官方文档:官方参考文献1 官方参考文献2
为了更好的理解类型编码, 做了一个小测试
@interface YYPerson : NSObject
// T:i N:"" V:_age
@property (nonatomic, assign) int age;
// T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"" R:"" C:"" N:"" V:_name
@property (nonatomic, copy, readonly) NSString<UITableViewDelegate, UITabBarDelegate> *name;
// T:@"NSString" &:"" N:"" V:_address
@property (nonatomic, retain) NSString *address;
// T:@"NSArray" W:"" N:"" V:_itemsArray
@property (nonatomic, weak) NSArray *itemsArray;
// T:@ &:"" N:"" V:_itItem
@property (nonatomic, strong) id itItem;
// T:@? C:"" N:"" V:_blockName
@property (nonatomic, copy) void(^blockName)(void);
@end
- (void)runtimeGetPropertyInfo {
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
unsigned int attrCount = 0;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (int j = 0; j < attrCount; j++) {
objc_property_attribute_t attr = attrs[j];
const char *attrName = attr.name;
const char *attrValue = attr.value;
NSString *attrStrValue = [NSString stringWithUTF8String:attrValue];
NSLog(@"%s--%s:%@", name, attrName, attrStrValue.length > 0 ? attrStrValue : @"\"\"");
}
}
}
通过property_copyAttributeList获取和属性相关的类型编码, 在代码中已经将log信息做了注释, T对应的是属性的类型, 比如name属性是NSString类型, 同时还遵循了UITableViewDelegate UITabBarDelegate协议, 那么name的编码为"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"", 在YYModel中有对这部分做相应的处理.
来看一下YYModel中的自定义的类型编码类型
typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
// 0000 0000 0000 0000 1111 1111
// 属性类型
YYEncodingTypeMask = 0xFF, ///< mask of type value
YYEncodingTypeUnknown = 0, ///< unknown
YYEncodingTypeVoid = 1, ///< void
...
YYEncodingTypeCArray = 22, ///< char[10] (for example)
// 0000 0000 1111 1111 0000 0000
YYEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier
...
YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway
// 1111 1111 0000 0000 0000 0000
// 属性修饰符相关
YYEncodingTypePropertyMask = 0xFF0000, ///< mask of property
...
YYEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic
};
由于代码较多, 只给出了部分代码, 完整代码还需查看YYModel源码.
使用NS_OPTIONS而非使用NS_ENUM定义枚举类型, NS_OPTIONS表示枚举选项可以多选, 而NS_ENUM表示枚举类型是互斥关系.
YYEncodingType中定义了三个互不关联的枚举类型, 通过YYEncodingTypeMask``YYEncodingTypeQualifierMask``YYEncodingTypePropertyMask三个掩码标识实现互不干扰, 通过&按位与的操作取出对应位的值(YYEncodingTypeMask & YYEncodingTypeBool)
三个掩码标识的二进制如下:
0000 0000 0000 0000 1111 1111
0000 0000 1111 1111 0000 0000
1111 1111 0000 0000 0000 0000
在YYModel中提供了一个函数, 将typeEncoding转换为YYModel自定义的枚举类型.
YYEncodingType YYEncodingGetType(const char *typeEncoding) {
...
switch (*type) {
case '@': {
// @?是block类型
if (len == 2 && *(type + 1) == '?')
return YYEncodingTypeBlock | qualifier;
else
return YYEncodingTypeObject | qualifier;
}
default: return YYEncodingTypeUnknown | qualifier;
}
}
在通过type判断对象类型时, 对block类型做了特殊判断(@?是block类型).
YYClassIvarInfo成员变量信息
YYClassIvarInfo用于通过runtime获取和成员变量相关的信息.
YYClassIvarInfo是对struct objc_ivar相关信息的封装, 在runtime源码中查看Ivar的数据结构:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name; // 成员变量名称
char * _Nullable ivar_type; // 成员变量的类型
int ivar_offset; // 成员变量在实例中的偏移量
};
可以通过Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)获取cls相关的成员变量信息.
unsigned int ivarCount = 0;
// 获取和类相关联的成员变量
Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivars[i];
// 获取成员变量信息
const char *name = ivar_getName(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
ptrdiff_t offset = ivar_getOffset(ivar);
}
// 需要手动释放
free(ivars);
YYClassIvarInfo中提供的属性接口:
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct, 成员变量
@property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name, 变量名称, _age格式
@property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset, 成员变量在实例变量内存中的偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding, 编码 见苹果官方文档
@property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type, 自定义枚举type
- (instancetype)initWithIvar:(Ivar)ivar;
@end
@implementation YYClassIvarInfo
- (instancetype)initWithIvar:(Ivar)ivar {
if (!ivar) return nil;
self = [super init];
_ivar = ivar;
// const char *修饰, name不可变
const char *name = ivar_getName(ivar);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
_offset = ivar_getOffset(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = YYEncodingGetType(typeEncoding);
}
return self;
}
@end
YYClassMethodInfo方法信息
YYClassMethodInfo用于通过runtime获取和方法相关的信息, 在runtime源码中查看objc_method的数据结构:
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name; // 方法名称
char * _Nullable method_types; // 方法类型
IMP _Nonnull method_imp; // 方法的实现
};
YYClassMethodInfo提供了和方法相关的信息
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; ///< method opaque struct, 方法
@property (nonatomic, strong, readonly) NSString *name; ///< method name, 方法名称
@property (nonatomic, assign, readonly) SEL sel; ///< method's selector, 方法选择器
@property (nonatomic, assign, readonly) IMP imp; ///< method's implementation, 方法实现
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types, 方法编码格式 "@16@0:8" "v16@0:8"
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type, 返回值类型 "@"
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type, 参数类型
- (instancetype)initWithMethod:(Method)method;
@end
@implementation YYClassMethodInfo
- (instancetype)initWithMethod:(Method)method {
if (!method) return nil;
self = [super init];
_method = method;
// -(void)runtimeGetIvarInfo;
// 获取方法选择器
_sel = method_getName(method);
// 获取方法实现
_imp = method_getImplementation(method);
// 获取方法名称 runtimeGetIvarInfo
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
// 方法编码 "@16@0:8"
const char *typeEncoding = method_getTypeEncoding(method);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
}
// 方法返回值类型 "@"
char *returnType = method_copyReturnType(method);
if (returnType) {
_returnTypeEncoding = [NSString stringWithUTF8String:returnType];
free(returnType);
}
// 参数个数 2 第一个参数是self 类型是@ 第二个参数是方法本身sel 类型是:
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);
NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
[argumentTypes addObject:type ? type : @""];
if (argumentType) free(argumentType);
}
_argumentTypeEncodings = argumentTypes;
}
return self;
}
@end
YYClassMethodInfo是对struct objc_method的封装提供了OC的访问方式, 注释中标明了以方法-(void)runtimeGetIvarInfo为例, 获取各种方法相关信息的值, 供参考.
YYClassPropertyInfo属性信息
YYClassPropertyInfo用于通过runtime获取和属性相关的信息, struct property_t的数据结构:
struct property_t {
const char *name;
const char *attributes;
};
objc_property_attribute_t数据结构:
typedef struct {
const char * _Nonnull name;
const char * _Nonnull value;
} objc_property_attribute_t;
YYClassPropertyInfo中提供的属性
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct 属性结构体
@property (nonatomic, strong, readonly) NSString *name; ///< property's name 属性名称
@property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type, 自定义枚举类型, 可以理解为属性的修饰符 nonatomic strong readonly等
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value 类型编码
@property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name 成员变量名称, 这里和属性名称还是有区别的, 只有类型编码为V的时候表示是实例变量, 成员变量才会被赋值
@property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil, 属性遵循的协议 可以理解为泛型
@property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) getter方法
@property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) setter方法
- (instancetype)initWithProperty:(objc_property_t)property;
@end
- (instancetype)initWithProperty:(objc_property_t)property {
...
for (unsigned int i = 0; i < attrCount; i++) {
switch (attrs[i].name[0]) {
// T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
// T:"@\"NSString\""
case 'T': { // Type encoding
if (attrs[i].value) {
// T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = YYEncodingGetType(attrs[i].value);
// 如果是实例对象类型
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
// @"@\"NSString<UITableViewDelegate><UITabBarDelegate>\""
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
// 获取属性类型
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
// 获取属性遵循的协议
NSMutableArray *protocols = nil;
while ([scanner scanString:@"<" intoString:NULL]) {
NSString* protocol = nil;
if ([scanner scanUpToString:@">" intoString: &protocol]) {
if (protocol.length) {
if (!protocols) protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
}
[scanner scanString:@">" intoString:NULL];
}
_protocols = protocols;
}
}
} break;
// 实例变量 _name
case 'V': { // Instance variable
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
} break;
default: break;
}
}
_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;
}
这里需要注意的是通过获取到的属性信息, 截取到属性的cls类型和遵循的协议protocols, 比如T:"@\"NSString<UITableViewDelegate><UITabBarDelegate>\"", 代码中已经给出了注释.
YYClassInfo类相关信息
YYClassInfo封装了类相关的信息,在runtime源码中查看 struct objc_class的数据结构:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; // isa指针
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE; // 父类
const char * _Nonnull name OBJC2_UNAVAILABLE; // 类名称
long version OBJC2_UNAVAILABLE; // 版本号
long info OBJC2_UNAVAILABLE; // 信息
long instance_size OBJC2_UNAVAILABLE; // 实例变量大小
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 成员变量列表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; // 方法列表
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 缓存列表
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 协议列表
#endif
} OBJC2_UNAVAILABLE;
cache主要存储的是方法缓存, 当一个实例变量需要调用方法时, 会先从缓存列表中查找方法, 提高方法的调用效率.
YYClassInfo提供的属性和方法信息
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object, 父类
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object, 元类
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class 是否是元类
@property (nonatomic, strong, readonly) NSString *name; ///< class name, 类名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info, 父类信息
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars, 成员变量列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods, 方法列表
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties, 属性列表
- (void)setNeedUpdate;
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
YYClassInfo中提供的构造方法
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
// 单例 classCache:当前类的缓存 metaCache: 元类缓存
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
// 创建单例, 一个类的相关信息是相同的, 进行缓存, 避免每次都通过runtime转换class, 提高性能.
// 信号量锁
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
// 加锁
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// 从缓存中取信息info
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
// 查看类是否需要更新信息
if (info && info->_needUpdate) {
// 类信息需要更新
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls];
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
dispatch_semaphore_signal(lock);
}
}
return info;
}
因为一个类的信息在被装载到内存以后, 类的信息是不经常发生变化的, 所以可以对类信息做相应的内存缓存, 提高查找效率.
代码中使用static修饰局部变量classCache和metaCache(关于static修饰变量可以查看汇编分析static), 在访问临界资源时使用信号量dispatch_semaphore_t加锁.
YYClassInfo中的更新操作
- (void)_update {
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;
Class cls = self.cls;
// 获取方法列表, 存储的是YYClassMethodInfo实例变量
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
if (methods) {
NSMutableDictionary *methodInfos = [NSMutableDictionary new];
_methodInfos = methodInfos;
for (unsigned int i = 0; i < methodCount; i++) {
YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
if (info.name) methodInfos[info.name] = info;
}
free(methods);
}
// 获取属性列表 存储的是YYClassPropertyInfo实例变量
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
if (properties) {
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i < propertyCount; i++) {
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
if (info.name) propertyInfos[info.name] = info;
}
free(properties);
}
// 获取成员变量列表 存储的是YYClassIvarInfo实例变量
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
if (ivars) {
NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
_ivarInfos = ivarInfos;
for (unsigned int i = 0; i < ivarCount; i++) {
YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
if (info.name) ivarInfos[info.name] = info;
}
free(ivars);
}
if (!_ivarInfos) _ivarInfos = @{};
if (!_methodInfos) _methodInfos = @{};
if (!_propertyInfos) _propertyInfos = @{};
_needUpdate = NO;
}
YYClassInfo中的更新其实就是重新获取了和类相关的成员变量列表 方法列表 属性列表, 并更新对应的ivarInfos methodInfos propertyInfos.
什么情况下需要更新呢? 比如当通过runtime动态向类中添加方法和属性等操作, 就需要更新类的信息.(成员变量在类信息中属于只读, 不能动态修改).
思考
- 类型编码 type-encoding
- runtime使用