前言
RunTime简称运行时,是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API,平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者,简单来说“就是系统在运行的时候的一些机制”
作用
那RunTime是怎么用的呢?
- 消息传递、转发
- 在程序运行过程中,可以动态的创建类,动态添加、修改这个类的属性和方法; (比如KVO的底层实现)
- 遍历一个类中所有的成员变量、属性、以及所有方法(如果你的成员变量私有,也可以获取到)
那应用的地方有哪些?
- 给分类添加属性,方法
- 方法的交换
- 字典转模型
- 获取对象的所有属性
- 动态创建类
- 当属性过多时,通过runtime便捷归解档
- KVC、KVO,block
- ...
相关概念
先了解类的本质和一些相关概念
Runtime简单粗暴理解
简单体验一下类底层代码、类的本质
将一段Object-C的代码用clang看下底层的C/C++的写法
typedef enum : NSUInteger {
Student = 0,
Worker = 1,
Programmer = 2,
}
ProfessionType;
@interface Person : NSObject
@property (copy,nonatomic) NSString *name;
@property (assign,nonatomic) ProfessionType type;
@end
@implementation Person
- (void)giveThePersonName:(NSString *)name{
self.name = name;
}
- (void)giveThePersonProgrammer:(ProfessionType)type{
self.type = type;
}
@end
使用命令,在当前文件夹中会出现Person.cpp的文件
# clang -rewrite-objc Person.m
简单研读
/*
* 顾名思义存放property的结构体
* 当我们使用perproty的时候,会生成这样一个结构体
* 具体存储的数据为
* 实际内容:"Name","T@\"NSString\",C,N,VName"
* 原型:@property (copy,nonatomic)NSString *Name;
**/
struct _prop_t {
const char *name; //名字
const char *attributes; //属性
};
/*
*类中方法的结构体,cmd和imp的关系是一一对应的关系
*创建对象生成isa指针,指向这个对象的结构体时
*同时生成了一个表"Dispatch table"通过这个_cmd的编号找到对应方法
*使用场景:
*例如方法交换,方法判断。。。
**/
struct _objc_method {
struct objc_selector * _cmd; //SEL 对应着OC中的@selector()
const char *method_type; //方法的类型
void *_imp; //方法的地址
};
/*
* method_list_t 结构体:
* 原型:
* - (void)GiveThisGameName:(NSString *)name;
* 实际存储的方式:
* {(struct objc_selector *)"GiveThisGameName:", "v24@0:8@16", (void *)_I_Game_GiveThisGameName_}
* 其主要目的是存储一个数组,基本的数据类型是 _objc_method
* 扩展:当然这其中有你的属性,自动生成的setter、getter方法
**/
static struct _method_list_t {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
}
/*
* 表示这个类中所遵守的协议对象
* 使用场景:
* 判断类是否遵守这个协议,从而动态添加、重写、交换某些方法,来达到某些目的
*
**/
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods; // 实例方法
const struct method_list_t *class_methods; //类方法
const struct method_list_t *optionalInstanceMethods; //可选的实例方法
const struct method_list_t *optionalClassMethods; //可选的类方法
const struct _prop_list_t * properties; //属性列表
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes; //扩展的方法类型
};
/*
* 类的变量的结构体
* 原型:
* NSString *Name;
* 存储内容:
* {(unsigned long int *)&OBJC_IVAR_$_Game$Name, "Name", "@\"NSString\"", 3, 8}
* 根据存储内容可以大概了解这些属性的工作内容
**/
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name; //名字
const char *type; //属于什么变量
unsigned int alignment; //未知
unsigned int size; //大小
};
/*
* 这个就是类中的各种方法、属性、等等信息
* 底层也是一个结构体
* 名称、方法列表、协议列表、变量列表、layout、properties。。
*
**/
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout; //布局
const char *name; //名字
const struct _method_list_t *baseMethods;//方法列表
const struct _objc_protocol_list *baseProtocols; //协议列表
const struct _ivar_list_t *ivars; //变量列表
const unsigned char *weakIvarLayout; //弱引用布局
const struct _prop_list_t *properties; //属性列表
};
/*
* 类本身
* oc在创建类的时候都会创建一个 _class_t的结构体
* 我的理解是在runtime中的object-class结构体在底层就会变成_class_t结构体
**/
struct _class_t {
struct _class_t *isa; //元类的指针
struct _class_t *superclass; //父类的指针
void *cache; //缓存
void *vtable; //表信息、未知
struct _class_ro_t *ro; //这个就是类中的各种方法、属性、等等信息
};
/*
* 类扩展的结构体
* 在OC中写的类扩展
**/
struct _category_t {
const char *name; //名称
struct _class_t *cls; //这个是哪个类的扩展
const struct _method_list_t *instance_methods; //实例方法列表
const struct _method_list_t *class_methods; //类方法列表
const struct _protocol_list_t *protocols; //协议列表
const struct _prop_list_t *properties; //属性列表
};
上述是Object-C中类中基本的数据,了解了类的定义,我们基本可以这么理解,类就是多个结构体组合的一个集合体,类中的行为、习惯、属性抽象,按照机器能懂的数据存储到我们底层的结构体当中,在我们需要使用的时候直接获取使用。
使用场景
- 动态添加属性
@implementation NSObject (Property)
- (void)setName:(NSString *)name
{
/*
object:保存到哪个对象中
key:用什么属性保存 属性名
value:保存值
policy:策略,strong,weak
*/
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, "name");
}
- 动态添加方法
//在Person.m文件中
#import "Person.h"
#import
@implementation Person
void eatFruit(id self, SEL _cmd) {
NSLog(@"动态添加了一个吃水果的方法");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == NSSelectorFromString(@"eatFruit")) {
class_addMethod(self, sel, (IMP)eatFruit, "v@:");
return YES;
}
// 先恢复, 不然会覆盖系统的方法
return [super resolveInstanceMethod:sel];
}
@end
//调用
Person *man = [[Person alloc] init];
[man performSelector:@selector(eatFruit)];
- 交换方法
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
Method Dm_imageNameMethod = class_getClassMethod(self, @selector(Dm_imageNamed:));
method_exchangeImplementations(imageNameMethod, Dm_imageNameMethod);
+ (instancetype)Dm_imageNamed:(NSString *)name {
// 这里调用Dm_imageNamed,相当于调用imageNamed
//可以根据自己的需求做处理
UIImage *image = [self Dm_imageNamed:name];
if (image == nil) {
NSLog(@"加载空的图片");
}
return image;
}
- 字典转模型
/* 用一个分类来现实 */
//创建一个NSObject+Category 文件
//NSObject+Category.h
#import
@interface NSObject (Category)
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary;
@end
//NSObject+Category.m文件
#import "NSObject+Category.h"
#import
@implementation NSObject (Category)
+ (NSArray *)getPropertList
{
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i< count; i++) {
//获取属性
objc_property_t property = propertyList[i];
//获取属性名称
const char *pName = property_getName(property);
NSString *name = [[NSString alloc] initWithUTF8String:pName];
//添加到数组
[array addObject:name];
}
//释放
free(propertyList);
return array.copy;
}
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary
{
id obj = [self new];
// 遍历属性数组
for (NSString *property in [self getPropertList]) {
if (dictionary[property]) {
// 使用 KVC 赋值
[obj setValue:dict[property] forKey:property];
}
}
return obj;
}
@end
- 快速归档解挡
//归档
- (void)encodeWithCoder:(NSCoder *)encoder {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (NSInteger i = 0; i < count; i++) {
objc_property_t property = propertyList[i];
//获取属性名称
const char *pName = property_getName(property);
NSString *name = [[NSString alloc] initWithUTF8String:pName];
id value = [self valueForKey:name];
[encoder encodeObject:value forKey:name];
}
free(propertyList); //释放
}
//解档
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (NSInteger i = 0; i < count; i++) {
objc_property_t property = propertyList[i];
//获取属性名称
const char *pName = property_getName(property);
NSString *name = [[NSString alloc] initWithUTF8String:pName];
id value = [decoder decodeObjectForKey:name];
[self setValue:value forKey:name];
}
free(propertyList); //释放
return self;
}
其实原理都是使用了RunTime的遍历属性,延展出来的各种操作。
结语
总体而已,RunTime在iOS中使用场景广泛,但并不难,只要理解了本质,就是成员变量,属性添加,查询,方法的替换等,只要遇到类似的问题,都可以从这几个方面去考虑,利用RunTime,高效的提升代码的延展性,高效快捷的处理问题。
引用的以下文章的部分内容
OC刨根问底】-Runtime简单粗暴理解
深入浅出Runtime (一) 什么是Runtime? 定义?
简述runtime的一些作用和使用场景