一. RunTime简介
RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数,如果调用未实现的函数就会报错。对于OC语言,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
二. RunTime消息机制
消息机制是运行时里面最重要的机制,OC中任何方法的调用,本质都是发送消息。使用运行时,发送消息需要导入框架<objc/message.h>
并且xcode5之后,苹果不建议使用底层方法,如果想要使用运行时,需要关闭严格检查objc_msgSend的调用,BuildSetting->搜索msg 改为NO。
下来看一下实例方法调用底层实现
Person *p = [[Person alloc] init];
[p eat];
// 底层会转化成
//SEL:方法编号,根据方法编号就可以找到对应方法的实现。
[p performSelector:@selector(eat)];
//performSelector本质即为运行时,发送消息,谁做事情就调用谁
objc_msgSend(p, @selector(eat));
// 带参数
objc_msgSend(p, @selector(eat:),10);
类方法的调用底层
// 本质是会将类名转化成类对象,初始化方法其实是在创建类对象。
[Person eat];
// Person只是表示一个类名,并不是一个真实的对象。只要是方法必须要对象去调用。
// RunTime 调用类方法同样,类方法也是类对象去调用,所以需要获取类对象,然后使用类对象去调用方法。
Class personclass = [Persion class];
[[Persion class] performSelector:@selector(eat)];
// 类对象发送消息
objc_msgSend(personclass, @selector(eat));
**@selector (SEL):是一个SEL方法选择器。**SEL其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。对于一个类中。每一个方法对应着一个SEL。所以一个类中不能存在2个名称相同的方法,即使参数类型不同,因为SEL是根据方法名字生成的,相同的方法名称只能对应一个SEL。
运行时发送消息的底层实现每一个类都有一个方法列表 Method List,保存这类里面所有的方法,根据SEL传入的方法编号找到方法,相当于value - key的映射。然后找到方法的实现。去方法的实现里面去实现。如图所示。
那么内部是如何动态查找对应的方法的?首先我们知道所有的类中都继承自NSObject类,在NSObjcet中存在一个Class的isa指针。
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
我们来到objc_class中查看,其中包含着类的一些基本信息。
struct objc_class {
Class isa; // 指向metaclass
Class super_class ; // 指向其父类
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类遵守的协议
}
下面我们就以p实例的eat方法来看看具体消息发送之后是怎么来动态查找对应的方法的。
- 实例方法
[p eat];
底层调用[p performSelector:@selector(eat)];
方法,编译器在将代码转化为objc_msgSend(p, @selector(eat));
- 在
objc_msgSend
函数中。首先通过p
的isa
指针找到p
对应的class
。在Class
中先去cache
中通过SEL
查找对应函数method
,如果找到则通过method
中的函数指针跳转到对应的函数中去执行。 - 若
cache
中未找到。再去methodList
中查找。若能找到,则将method
加入到cache
中,以方便下次查找,并通过method
中的函数指针跳转到对应的函数中去执行。 - 若
methodlist
中未找到,则去superClass
中查找。若能找到,则将method
加入到cache
中,以方便下次查找,并通过method
中的函数指针跳转到对应的函数中去执行。
三. 使用RunTime交换方法:
当系统自带的方法功能不够,需要给系统自带的方法扩展一些功能,并且保持原有的功能时,可以使用RunTime交换方法实现。这里要实现image添加图片的时候,自动判断image是否为空,如果为空则提醒图片不存在。方法一:使用分类
+ (nullable UIImage *)xx_ccimageNamed:(NSString *)name
{
// 加载图片 如果图片不存在则提醒或发出异常
UIImage *image = [UIImage imageNamed:name];
if (image == nil) {
NSLog(@"图片不存在");
}