一、runtime简介

  • Runtime 简称运行时。OC就是运行时机制,其中主要就是消息机制
  • 对于C语言,函数在编译期就会决定调用哪个函数。
  • 对于OC函数,属于动态调用,在编译期并不能真正决定调用哪个函数,只有在真正运行时候才会根据函数的名称找到对应的函数来调用。
  • 事实证明
    • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
    • 在编译阶段,C语言调用未实现的函数就会报错。

二、runtime作用

1.发送消息
  • 方法调用的本质,就是让对象发送消息。
  • objc_msgSend,只有对象才能发送消息,因此以objc开头。
  • 使用消息机制的前提,需导入#import <objc/message.h>
1
2
3
4
5
6
7
8
//创建person对象
Person *p = [[Person alloc]init];
//调用对象方法
[p eat];
//本质
objc_msgSend(p,@selector(eat));
2.交换方法

场景:系统自带的方法功能不够,给系统自带方法扩展一些功能,并保持原有功能。

  • 方法一:继承系统的类,重写方法。
  • 方法二:使用runtime,交换方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@implementation UIImage (imageWithName)
+ (void)load{
//交换方法
//获取imagwWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
//获取imageNamed方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
//交换方法地址,相当于交换方法的实现
method_exchangeImplementations(imageWithName, imageName);
}
+ (instancetype)imageWithName:(NSString *)name{
//这里调用imageWithName 相当于调用imageNamed(因为方法调换了)
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加载图片为空");
}
return image;
}

然后这里只需要在需要的地方调用+(UIImage)imageNamed:方法就可以了,例如:

1
2
3
4
5
6
7
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *image = [UIImage imageNamed:@"123"];
}

如果图片不存在的话,console就会输出加载图片为空

3.动态添加方法
1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc]init];
//person 没有实现eat方法,可以通过performSelctor调用
[p performSelector:@selector(eat)];
//[p eat];正常调用会报错
}
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
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
//void(*)()
//默认方法都有两个隐式参数
void eat(id self,SEL sel){
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
//这是NSObject根类提供的类方法,调用时机为当被调用的方法实现部分没有找到,而消息转发机制启动之前的这个中间时刻。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
//动态添加eat方法
/**
* 动态添加方法
*
* @param self cls:为哪个类添加方法
* @param sel SEL:添加方法的方法编号(方法名)是什么
* @param IMP IMP:方法实现
* @param const char * types方法类型
*
* @return 返回是否添加成功
*/
class_addMethod(self, sel, (IMP)eat, "v@:@");
//class_addMethod(self, @selector(eat), eat, "v@:@");
}
return [super resolveInstanceMethod:sel];
}
@end

值得注意的是,最后一个参数@param const char *types不太常用,通过官方文档查阅得知如下:

4.动态添加属性
  • category中只能添加方法而不能直接添加属性,所以我们常常需要动态地来添加属性。
  • 给一个类声明属性,其本质是给这个类添加关联,而不是直接把这个值得内存空间添加到类的内存空间中去。
1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//给系统的NSObject类动态添加属性name
NSObject *object = [[NSObject alloc]init];
object.name = @"壮壮";
NSLog(@"%@",object.name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation NSObject (property)
- (NSString *)name{
//根据关联的key,获取关联的值
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name{
/**
参数1:给哪个对象添加关联
参数2:关联的key,通过这个key来获取
参数3:管理的value
参数4:关联的策略
*/
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end