Objective-C Runtime 初识(二)

By | 2016-02-27

前面了解了类的基本布局,这次来了解一下 OC 中的消息处理,之前也知道 OC 上所有方法的调用实际上都是 objc_msgSend 函数的调用,但是具体怎么调用的没有深入了解,这次来学习学习

消息中用到的数据类型

Method

类中的方法类型,是一个 objc_method 结构体指针:

typedef struct objc_method *Method;
struct objc_method {
     SEL method_name OBJC2_UNAVAILABLE;
     char *method_types OBJC2_UNAVAILABLE;
     IMP method_imp OBJC2_UNAVAILABLE;
}
SEL

方法的方法名,是一个 objc_selector 结构体指针:

typedef struct objc_selector *SEL;

objc_selector 结构体不透明,但是可以理解成一个 C 语言的字符串,运行时中维护了一张 SEL 的表,它把相同名字的方法名映射到了一个唯一的 SEL 上,不同的类中如果方法名相同他们的 SEL 也是相同的,同一个类中不能有两个相同的 SEL ,也就是 OC 不能支持重载。

SEL 的三种获取方式:

  • sel_registerName(char *name)
  • NSSelectorFromString(NSString *aSelectorName)
  • @selector(selector)
IMP

函数指针,OC 中的方法最终都会被转化为 C 语言的函数,IMP 就是指向这些函数首地址的指针。可以通过 method_getImplementation 获取 IMP 之后直接进行函数调用跳过 OC 的消息发送过程。

objc_msgSend

OC 中的方法调用 [object method] ,编译器会将它转化为下面的函数调用:

id objc_msgSend(id self, SEL _cmd, ...);

这个函数有两个默认参数:

  • self 就是消息的发送者,这也是为什么我们在方法中可以直接调用 self 的原因,对象方法中 self 是对象,类方法中 self 就是类对象了。
  • _cmd 是 SEL 类型的,当前方法的选择器,消息发送过程中会通过它查找 IMP ,最后做函数调用。

obj_msgSend 是用汇编实现的,借用 伪代码 加深一下理解:

id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //调用这个函数,伪代码...
}

//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //这个是用于消息转发的
    return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }

    Class curClass = cls;
    IMP imp = nil;
    do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);

    return imp;
}

看到伪代码就很容易理解 obj_msgSend 所做的事了:

  1. 先判断消息的调用对象是否存在,不存在就直接返回,所以可以像 nil 发送任意消息而不会有什么副作用
  2. 对象的 isa 中查找 IMP,即如果是对象就在它的类对象的方法列表中查找,如果是类对象就在它的元类对象的方法列表中查找。查找 IMP 的过程如下:
    1. 判断这个类对象和选择子是否存在,不存在就直接返回 nil
    2. 先查找这个类对象的方法缓存列表,如果缓存列表中没有,去这个类对象的方法列表中查找,如果依旧找到就递归它的父类继续查找
    3. 如果没有找到 IMP 就返回一个消息转发函数 _objc_msgForward
    4. 返回这个最终的 IMP
  3. 调用这个 IMP,如果正常在这个对象的类对象中查找到了 IMP ,那么这个消息发送就完成了,如果返回的是 _objc_msgForward 就要开始消息转发流程了

消息转发

给一个对象发送消息时,如果这个对象无法响应这个方法,即它的类对象中找不到相应的 IMP ,最后返回了 _objc_msgForward 开始进入消息转发,消息转发主要有三个阶段。

动态方法解析

可以重写下面两个方法分别对类方法和对象方法做动态方法解析:

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;

允许用户在这里动态的添加方法实现,如果实现了,调用并返回,如果没有实现就继续下一个阶段。

备援接收者

这一阶段中,Runtime 会通过下面的方法询问是否能将这个消息转发给其他的接收者:

- (id)forwardingTargetForSelector:(SEL)aSelector;

如果这里返回了一个备援接收者,就开始新的消息发送过程,如果没有备援接收者就进入完整的消息转发流程。

在这个阶段就可以实现 OC 的多重继承了。

完整的消息转发

在这个阶段,Runtime 会先调用 methodSignatureForSelector: 方法获取方法签名,如果获取不到方法签名就抛出异常。

将获取到的方法签名转换为一个 NSInvocation 的对象传入下面的方法中:

- (void)forwardInvocation:(NSInvocation *)anInvocation;

在这个方法中就可以调用其他对象的其他方法开始完整的消息转发了。

References: