RunTime简介
因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。
RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。
RunTime中主要使用的函数定义在message.h和runtime.h
这两个文件中。 在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。
使用时,需要导入文件,导入如:
#import <objc/message.h>
#import <objc/runtime.h>
函数的定义
- 对
对象
进行操作的方法一般以object_
开头 - 对
类
进行操作的方法一般以class_
开头 - 对
类或对象的方法
进行操作的方法一般以method_
开头 - 对
成员变量
进行操作的方法一般以ivar_
开头 - 对
属性
进行操作的方法一般以property_
开头开头 - 对
协议
进行操作的方法一般以protocol_
开头
根据以上的函数的前缀 可以大致了解到层级关系。
对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。
RunTime使用
一、runtime本质,消息发送
方法调用的本质,就是让对象发送消息。
objc_msgSend,只有对象才能发送消息,因此以objc开头.
使用消息机制前提,必须导入#import <objc/message.h>
代码例子:
// 创建person对象
Person *p = [[Person alloc] init];
// 调用对象方法
[p eat];
// 底层实现:让对象发送消息
objc_msgSend(p, @selector(eat)); // Xcode5之后这里需要把 targets -> bulid Settings 搜索 msg 把objc_msgSend Calls YES 修改为 NO
// 调用类方法的方式:三种
// 第一种通过类名调用
[Person eat];
// 第二种通过类对象调用
[[Person class] eat];
// 第三种调用方法
[Person performSelector:@selector(eat)];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 底层实现 让类对象发送消息
objc_msgSend([Person class], @selector(eat));
/**
还有,我是怎么知道上面的方法的本质demo的呢,
我们可以通过clang 命令来查看代码生成的CPP代码。
最终代码,需要把当前代码重新编译,用xcode编译器,clang
clang -rewrite-objc main.m 查看最终生成代码
**/
二、runtime方法交换
交换方法实现的需求场景:
1、自己创建了一个功能性的方法,在项目中多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,要求是不改变旧的项目(也就是不改变原来方法的实现)。
2、可以做一些异常处理,防止APP崩溃处理,(数组越界等等)
可以在类的分类中,再写一个新的方法(是符合新的需求的),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码 的情况下,就完成了项目的改进。
交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。
下面demo是从这里看到的
@implementation NSMutableArray (Safe)
+ (void)load
{
[NSClassFromString(@"__NSArrayM") swapMethod:@selector(objectAtIndex:)
currentMethod:@selector(ls_objectAtIndex:)];
}
// 实例方法的交换
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
Method firstMethod = class_getInstanceMethod(self, originMethod);
Method secondMethod = class_getInstanceMethod(self, currentMethod);
method_exchangeImplementations(firstMethod, secondMethod);
}
- (id)ls_objectAtIndex:(NSUInteger)index
{
if (index >= self.count)
{
NSLog(@"数组越界;;;;;;;;;;;;");
return nil;
}
return [self ls_objectAtIndex:index];
}
@end
三、获取相关参数
1、获取成员变量,包括属性生成的成员变量
+ (NSArray *)fetchIvarList
{
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
const char *ivarName = ivar_getName(ivarList[i]);
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
dic[@"type"] = [NSString stringWithUTF8String:ivarType];
dic[@"ivarName"] = [NSString stringWithUTF8String:ivarName];
[mutableList addObject:dic];
}
free(ivarList);
return [NSArray arrayWithArray:mutableList];
}
2、获取类的属性列表,包括私有和公有属性,也包括分类中的属性
+ (NSArray *)fetchPropertyList
{
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
const char *propertyName = property_getAttributes(propertyList[i]);
[mutableList addObject:[NSString stringWithUTF8String:propertyName]];
}
free(propertyList);
return [NSArray arrayWithArray:mutableList];
}
3、 获取对象方法列表:包括getter, setter, 分类中的方法等
+ (NSArray *)fetchInstanceMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
4、获取类方法列表 包括分类里面的
+ (NSArray *)fetchClassMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(object_getClass(self), &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
5、获取协议列表,包括.h .m 和分类里的
+ (NSArray *)fetchProtocolList
{
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ )
{
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String:protocolName]];
}
return [NSArray arrayWithArray:mutableList];
}
四、关联对象
关联对象不是为类\对象添加属性或者成员变量(因为在设置关联后也无法通过ivarList或者propertyList取得) ,而是为类添加一个相关的对象,通常用于存储类信息,例如存储类的属性列表数组,为将来字典转模型的方便。
1、对象关联属性
列如,oc里面,类没有name 这个参数,我们可以给他添加一个name
// 定义关联的key
static const char *key = "name";
@implementation NSObject (RunTime)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
// 调用
// NSObject对象添加属性
NSObject * object = [[NSObject alloc] init];
object.name = @"gameover";
NSLog(@"object name =====%@",object.name);
// 打印 如下
//2018-06-28 14:52:54.302050+0800 Test_runTime[23578:9984095] object name ===== gameover
2、对象关联对象
动态添加方法会用到这两个api
objc_setAssociatedObject
第一个参数 id object, 当前对象
第二个参数 const void *key, 关联的key,是c字符串
第三个参数 id value, 被关联的对象的值
第四个参数 objc_AssociationPolicy policy关联引用的规则
objc_getAssociatedObject
第一个参数 id object, 当前对象
第二个参数 const void *key, 关联的key,是c字符串
- (void)viewDidLoad {
// 对象添加关联对象
// 列如btn 对象添加多个参数,
UIButton * btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 40)];
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
// 传递多参数
objc_setAssociatedObject(btn, "yt_text", @"你好", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(btn, "yt_size", @"15", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 点击按钮触发
- (void)btnClick:(UIButton*)btn
{
NSString *yt_text = objc_getAssociatedObject(btn, "yt_text");
NSString *yt_size = objc_getAssociatedObject(btn, "yt_size");
NSLog(@"yt_text ==%@ yt_size ==%@",yt_text,yt_size);
}
// 打印 如下
//2018-06-28 14:50:32.083111+0800 Test_runTime[23575:9981192] yt_text ==你好 yt_size ==15
五、动态添加方法
在开发中:如果一个类的方法非常多的时候,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决
动态添加方法会用到这个api
class_addMethod
第一个参数:给哪个类添加方法
第二个参数:添加方法的方法编号
第三个参数:添加方法的函数实现(函数地址)
第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
在使用[p performSelector:@selector(yt_sleep) withObject:nil];
或者
的时候,由于 yt_sleep
这个方法在Person.m里面是没有实现
的
所以,会崩溃。
所以只要在下面两个方法里面分别动态添加方法即可避免该问题
在调用没有实现的实例方法
会触发这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
在调用没有实现的类方法
会触发这个方法
+(BOOL)resolveClassMethod:(SEL)sel;
#import <Foundation/Foundation.h>
@interface Person : NSObject
+ (void)eat;
- (void)eat;
+ (void)yt_sleep;
@end
#import "Person.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Person
- (void)eat
{
NSLog(@"eat 方法调用");
}
+ (void)eat
{
NSLog(@"eat 类方法调用");
}
//+ (void)yt_sleep
//{
// NSLog(@"sleep 类方法调用");
//}
//当类调用一个没有实现的类方法就会到这里!!
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"类方法 %@",NSStringFromSelector(sel));
if (sel == @selector(yt_sleep)) {
/*
第一个参数:给哪个类添加方法
第二个参数:添加方法的方法编号
第三个参数:添加方法的函数实现(函数地址)
第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
*/
//class_addMethod(self, @selector(yt_sleep), (IMP)yt_sleep, "v@:"); // 这样会崩溃
class_addMethod(objc_getMetaClass("Person"), @selector(yt_sleep), (IMP)yt_sleep, "v@:");
}
return [super resolveClassMethod:sel];
}
//当类调用一个没有实现的对象方法就会到这里!!
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"实例方法 %@",NSStringFromSelector(sel));
if (sel == @selector(yt_sleep)) {
// 动态添加eat方法
/*
第一个参数:给哪个类添加方法
第二个参数:添加方法的方法编号
第三个参数:添加方法的函数实现(函数地址)
第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
*/
class_addMethod(self, sel, (IMP)yt_sleep, "v@:");
}
return [super resolveInstanceMethod:sel];
}
// 默认方法都有两个隐式参数,
void yt_sleep(id self,SEL sel)
{
NSLog(@"%@ %@ 睡觉了",self,NSStringFromSelector(sel));
}
@end
//其他地方调用
// 动态添加实例方法
[p performSelector:@selector(yt_sleep) withObject:nil];
// 动态添加类方法
[Person performSelector:@selector(yt_sleep) withObject:nil];
六、字典转模型
MJExtension 里面就使用runTime来完成这些相应的操作的
下面是我的伪代码(没有mj那么精细)
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic ,strong) NSString * id;
@property (nonatomic ,strong) NSString * name;
@property (nonatomic ,strong) NSString * sex;
@property (nonatomic ,strong) NSString * age;
@property (nonatomic ,strong) NSString * height;
@property (nonatomic ,strong) NSString * weight;
+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end
#import "User.h"
#import <objc/runtime.h>
@implementation User
// Ivar:成员变量 以下划线开头
// Property:属性
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
// runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
// 1.获取模型中所有成员变量 key
// 获取哪个类的成员变量
// count:成员变量个数
unsigned int count = 0;
// 获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 获取key
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找对应value
// key:user value:NSDictionary
id value = dict[key];
// 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型
// 并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典转换成模型 userDict => User模型
// 转换成哪个模型
// 获取类
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}
// 给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
// 其他地方调用
// 字典转模型
NSDictionary * dict = @{ @"id" : @"1235",
@"name" : @"帅哥",
@"sex" : @"男",
@"age" : @"18",
@"height" : @"180cm",
@"weight" : @"70kg",
@"good" : @"eat",
@"money" : @"10000",
@"sport" : @"basketball"};
User * user = [User modelWithDict:dict];
NSLog(@"id =%@ name =%@ sex =%@ age =%@ height =%@ weight =%@",user.id,user.name,user.sex,user.age,user.height,user.weight);
打印如下
2018-06-28 17:00:07.724910+0800 Test_runTime[23654:10029472] id =1235 name =帅哥 sex =男 age =18 height =180cm weight =70kg
以上是本人的浅见,如有错误,望各位多多指正😘