什么是runtime?
Objective-C 是一门动态语言,相比C语言,是在其基础上增加了面向对象的特性和消息传递机制。而面向对象的特性和消息传递机制实现的关键就是runtime,也就是常说的运行时机制。
当我们平时使用OC去调用一个方法时,其实是在运行时通过runtime给对象发消息。那么这里的发消息和我们C语言里去调用一个函数有什么区别呢?在C语言里我们去调用一个方法(函数),其实是就是去调用内存中的一段代码,这里的函数名其实是和内存中的代码段绑定在一起的,而且这个绑定过程在编译阶段就已经确定下来了。而在OC中我们去调用一个对象的方法,并不会立即去执行这个方法,而是会先向这个对象发送一个消息,而具体调用的代码在这个时候是不确定的,因为最后调用的方法可能会是这个对象的其它的方法,也有可能是其他对象的方法。在这期间决定最后调用的方法的过程就是消息传递的过程。
runtime的消息传递过程
在了解消息传递过程之前,我们要先知道接收消息的对象本质是什么,我们看一下objc.h中objc_object的定义
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
看上面的代码我们知道我们创建的对象其实是一个struct objc_object 结构体,我们平常使用的id其实也是这个结构体,这个结构体中只有一个变量,一个Class类型的变量,这个变量的是一个struct objc_class结构体。
在消息传递的过程中,runtime会先通过object的isa指针来找到这个对象所属的类,也就是上面的struct objc_class,然后在这个类对象的方法列表中去寻找。这里我们先了解一下struct objc_class里有哪些东西:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#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和methodLists两个参数,而查找方法也是在这两个变量中去找的。为了优化性能和缩短方法的查找时间,objc_class的cache结构体中存储的是每一次类或者实例对象调用的方法。
1.当类或者实例对象调用方法时,会优先在cache中寻找相应的方法,如果找到了,则直接去找method的IMP实现并执行。如果找不到那就methodLists中去遍历查找,如果在methodLists找到了则会执行该方法IMP,并且把method的method_name作为key,method的IMP作为value保存到objc_cache中。
PS:这里说一下IMP是什么,IMP函数指针指向了方法实现的首地址,当 Objective-C发起消息时,最终执行的方法是由IMP决定的。
2.如果在methodLists中没有找到方法,则会去根据super_class获取到当前类的父类,重复1中的查找(如果是类方法则去找该类的元类)。如果找到了则返回,否则继续向上查找。
3.如果找到NSObject类还没有找到,则会进入消息转发阶段。
消息转发:
1.动态方法解析
动态解析,当没有找到方法时,Runtime为我们提供了一次动态添加方法实现的机会,让你去提供一个函数的实现。如果你实现了这两个方法并添加了新的方法,那么runtime会重新启动一次消息发送过程。
具体实现主要通过下面的三个方法
// 实现这个方法来添加新的类方法
+ (BOOL)resolveClassMethod:(SEL)sel;
// 实现这个方法来添加新的对象方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/**
运行时方法:向指定类中添加特定方法实现的操作
@param cls 被添加方法的所属的类
@param name 被添加方法的selector方法名
@param imp 被添加方法的实现
@param types 被添加方法的返回值与参数类型
@return 添加方法成功还是失败
*/
BOOL class_addMethod(Class _Nullable cls,
SEL _Nonnull name,
IMP _Nonnull imp,
const char * _Nullable types)
SEL:SEL是表示一个方法的selector指针,OC会根据不同方法的名字和,参数序列,生成唯一的标识,也就是这个指针的地址,所以就不难理解为什么OC中不能定义方法名相同但参数不同的方法了。
2.消息接受者重定向
-(id)forwardingTargetForSelector:(SEL)aSelector;
如果你没有在动态方法解析时添加方法,那接下来runtime会检查你是否实现了(id)forwardingTargetForSelector:(SEL)aSelector方法,如果实现了该方法则会将消息转发给你返回的对象,重复上面的消息查找过程。
3.消息重定向
// 通过方法调用者创建方法签名,然后将这个签名通过NSInvocation封装成方法调用传给重定向方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 拿到传过来的invocation参数,再次进行IMP查找
- (void)forwardInvocation:(NSInvocation *)anInvocation;
如果前面的两次机会都没有挽回,这时runtime会启动完整的消息转发机制
1.先发送- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector消息,拿到方法的参数和返回值信息,如果实现了该方法,并返回一个方法签名,runtime就会创建一个 NSInvocation对象并发送给- (void)forwardInvocation:(NSInvocation *)anInvocation方法通知该对象,给予此次消息发送的最后一次寻找IMP的机会。
2.如果没有实现该方法或返回值为nil,那么runtime则会发出doesNotRecognizeSelector消息,控制台输出unrecognized selector sent to instance同时直接Crash掉。
至此整个消息传递过程就结束了,这里总结一下:
1.我们创建的对象是一个objc_object结构体
2.调用方法时其实是通过runtime给对象发消息
3.消息的传递过程是根据结构体的Class参数去寻找所属类,并去cache和methodLists里去查找。
3.如果没找到则开始进行 消息转发机制(消息动态解析,消息接受者重定向,消息重定向)
网友评论