Runtime

94 阅读6分钟

runtime

OC是动态编程语言,有很多运行时/动态特性,不仅需要编译环境,还需要一个运行时系统(runtime system)来执行编译好的代码,运行时系统相当于OC的操作系统,完成对象生成、释放时的内存管理,为发来的消息查找对应的处理方法等。

OC语言编译后都是Runtime形式的C++代码,runtime即运行时,

类(Class)和对象:

  • objc_object代表类的实例对象,是一个结构体,成员仅有Class类型的isa;
  • objc_class 是一个结构体继承自objc_object,包含了 Class类型的isa,Class的父类,Class的name,Class的成员变量(链表结构体),Class的方法定义(链表结构体methodLists),Class的方法的缓存(cache),Class的协议(链表结构体)
  • 类定义:类(Class)是一个指向objc_class结构体的指针 typedef struct objc_class *Class;
    综上可以知道,isa是一个指向objc_class结构体的指针,通过isa可以得到Class的各种信息;而该结构体是继承自objc_object,说明objc_class也是一个对象。则可以通过isa在这条继承链上查找需要的信息。类的实例对象obj的isa指向类,类的isa指向元类,直至根NSObject。而NSObject的isa指向自身。

image.png

消息发送机制

image.png

  • 方法名(selector)被编译后会被一个数据类型为SEL(消息选择器)的内部标识符替代
    • 通过@selector 可以操作编译后的selector
    • performSelector:(SEL)selector
    • SEL类型就相当于方法名,根据消息接收者的不同来动态执行不同的方法;通过SEL类型来指定要执行的方法,这就是OC发送消息的方式,也是通过这样实现了OC的动态性
      • 对象收到消息后,执行哪个方法是动态决定的。所有的实例变量都存在isa;当收到消息后,运行时系统会检查类内是否有和这个消息选择器名字一样的方法,如果有就执行,没有就通过类对象中指向父类的isa找父类是否有,没有则逐层向父类找,直到根,找不到就提示执行时错误。
      • 如果每次收到消息都要查找对应的方法,消息发送过程开销很大;针对这个情况,运行时系统内部缓存一个散列表,来记录和消息选择器对应的方法、方法被定义在何处等信息;下次收到同样的消息时,找缓存即可
      • NSObject定义了可以动态查询一个对象是否能够响应某个选择器的方法

image.png

  • 具象来说:代码编译完,调用方法会被转换为Runtime形式代码 objc_msgSend(p, SEL op, ...),指向接收者p,发送方法名为op的消息,后面带的为参数列表。
    • 当对象收到消息时,即调用了类的实例方法,如[obj getName]//obj是某个类的对象,getName是实例方法; 会通过对象的isa指针找到类的methodLists,查找是否有对应的方法,如果没有会顺着继承关系向父类的methodLists查找,直至根类。【查找本质就是遍历methodLists链表,将SEL与methodLists结构体中的name字段进行对比,相等则将IMP指针返回】
    • 当类对象收到消息时,即调用类的类方法,如[classA getClassName];// classA是类,getClassName是类方法,会通过类对象的isa指针找到元类的methodLists,查找是否有对应的方法,若无,会沿着元类的继承关系向上查找,直到根类。
    • 在这个过程中,若找到这个方法,会存到类结构的obj_cache中,方便下次查找使用。

当找不到方法时,正常情况下runtime会抛出异常,但在抛出异常前,runtime会进行三次消息转发

消息转发

第一次消息转发(动态方法解析)

  • 转发到以下方法:在重写类的这两个类方法,按消息类别重写对应的方法,
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {}  (类方法)

// 如找不到**类A**的实例方法resolveMethodDynamically,则第一次转发到resolveInstanceMethod,若resolveInstanceMethod内处理了(class_addMethod)且放回YES,则可以找到消息响应
void dynamicIMP()//方法实现
{
    NSLog(@"动态解析方法");
}

+ (BOOL)resolveInstanceMethod:(**SEL**)sel//重写类的resolveInstanceMethod
{
    if (sel == @selector**(resolveMethodDynamically)) {
        class_addMethod([self class], sel, (IMP)dynamicIMP , "v@:");
        return YES;//返回YES
    }
    return [super resolveInstanceMethod:sel];
}

第二次消息转发(完整的消息转发机制)

上述代码块表示了第一次转发,成功,若resolveInstanceMethod 方法return NO,则进入完整的消息转发机制;分为快速消息转发和普通消息转发

  • 第二次消息转发(快速消息转发)
    • 若实现了forwardingTargetForSelector,系统会进入该方法处理消息,把之前没办法处理的消息(找不到的方法)转发给别的对象处理
- (id)forwardingTargetForSelector:(**SEL**)aSelector  //在类A内实现该方法
{
    if (aSelector == @selector(resolveMethodDynamically)) {
        return [Dog new];   // 返回别的一个类的对象,该类内实现了resolveMethodDynamically
    }
    return nil;
}
  • 第三次消息转发(普通消息转发) -若上一步也没对消息进行处理,会进入最后一步,系统会调用methodSignatureForSelector返回一个方法签名,获取函数的参数和返回值(若返回nil会抛出出异常),然后系统调用forwardInvocation,在这个方法中将消息转发给方法内指定的单个或多个对象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(resolveMethodDynamically)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    Dog *dog = [Dog new];    // 创建一个别的类的实例对象去转发消息
        if ([dog respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:dog];
        }
    Student *student = [Student new];   // 可以创建多个类的实例对象去转发消息
    if ([student respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:student]; // 转发给student处理
        }
}

总结

  1. 动态方法解析:由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。
  2. 快速消息转发:其他对象,使用范围更广,不只是限于原来的对象。
  3. 普通消息转发:它一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。可以转发给多个对象

同时需要注意的是,消息转发过程中,步骤越往后,处理消息的代价就越大,最好能在第一步就处理完,这样的话,运行期系统可以将此方法缓存。如果这个类的实例还会再接收到同名选择子,那么根本无须再次启动消息转发流程。

参考链接

zhuanlan.zhihu.com/p/487502836 // 涉及汇编 www.jianshu.com/p/ee19f969c… // 理论 + 代码 www.jianshu.com/p/54f031110… // 理论 + 代码 juejin.cn/post/692135… // 实践