runtime 笔记(方法调用-消息传递过程)

什么是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;

上面代码中我们可以看到有cachemethodLists两个参数,而查找方法也是在这两个变量中去找的。为了优化性能和缩短方法的查找时间,objc_classcache结构体中存储的是每一次类或者实例对象调用的方法。

1.当类或者实例对象调用方法时,会优先在cache中寻找相应的方法,如果找到了,则直接去找methodIMP实现并执行。如果找不到那就methodLists中去遍历查找,如果在methodLists找到了则会执行该方法IMP,并且把methodmethod_name作为key,methodIMP作为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参数去寻找所属类,并去cachemethodLists里去查找(没找到的话则根据super_class去父类里找,直到查找到NSObject)。
4.如果还没找到则开始进行 消息转发机制(消息动态解析,消息接受者重定向,消息重定向)

至此,我们对runtime的整个消息传递应该有个轮廓了,希望能帮助到大家

推荐阅读更多精彩内容

  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,004评论 0 7
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 491评论 0 2
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 669评论 0 1
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 742评论 0 4
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 791评论 0 6