[关闭]
@qidiandasheng 2016-09-02T13:24:21.000000Z 字数 2647 阅读 2276

iOS中的消息

iOS理论


前言

说到消息就要分两块:一块是消息发送,还有一块就是消息转发。

消息发送就是Runtime 通过 selector 快速查找 IMP(函数指针) 的过程。
消息转发就是在消息发送中找不到IMP然后进入一系列转发流程的过程。

objc_method

说道消息我们肯定需要说一下方法,方法在runtime中的结构体为objc_method,具体定义在runtime源码runtime.h文件中,如下:

  1. struct objc_method {
  2. //方法名
  3. SEL method_name OBJC2_UNAVAILABLE;
  4. //方法类型,描述了参数的类型
  5. char *method_types OBJC2_UNAVAILABLE;
  6. //函数指针,为方法具体实现代码块的地址
  7. IMP method_imp OBJC2_UNAVAILABLE;
  8. }

可以通过- (IMP)methodForSelector:(SEL)aSelector;获取实例方法的函数指针。
通过+ (IMP)instanceMethodForSelector:(SEL)aSelector获取类方法的函数指针。

这里我们能通过获得IMP直接调用方法:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. IMP imp = [self methodForSelector:@selector(printHellWorld)];
  4. imp();
  5. }
  6. - (void)printHellWorld{
  7. NSLog(@"Hell World");
  8. }

打印结果:

  1. 2016-09-02 19:53:03.250 test[46586:7508586] Hell World

objc_msgSend消息发送

消息的发送是通过objc_msgSend来的,他的伪代码如下所示:

  1. id objc_msgSend(id self, SEL _cmd, ...) {
  2. Class class = object_getClass(self);
  3. IMP imp = class_getMethodImplementation(class, _cmd);
  4. return imp ? imp(self, _cmd, ...) : 0;
  5. }

我们可以看到他通过class(类)和_cmd(方法子)来获取到这个函数指针,然后执行对应的函数。

我们在分析class的结构objc_class是看到有两个属性:struct objc_method_list **methodListsstruct objc_cache *cache

在查找IMP的过程中,首先会根据SEL作为key去类的cache方法缓存中查找,如果找到直接返回IMP,如果找不到就去methodLists方法列表中查找,如果找到返回IMP(同时把方法加入到cache中,方便下次快速查找)。
如果找不到就去父类中找循环以上的过程,直到到最顶层的NSObjec中都找不到IMP了,就会开始准备进入方法转发的过程。

消息转发

消息转发可以间接实现多继承。

我们看到上面objc_method的伪代码中有这么一段class_getMethodImplementation函数来得到IMP,我们来看看这个函数的定义(在objc-class.mm文件中):

  1. IMP class_getMethodImplementation(Class cls, SEL sel)
  2. {
  3. IMP imp;
  4. if (!cls || !sel) return nil;
  5. imp = lookUpImpOrNil(cls, sel, nil,
  6. YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
  7. // Translate forwarding function to C-callable external version
  8. if (!imp) {
  9. return _objc_msgForward;
  10. }
  11. return imp;
  12. }

我们能看到当找不到imp的时候此函数返回了一个_objc_msgForward。其实_objc_msgForward也是函数指针,只是他是在类及父类中找不到对应的方法时才返回,主要就是用于消息转发。
如果直接使用_objc_msgForward相当于就是跳过查找类中的IMP的过程,直接进行消息转发。就算这个类中有这个方法,也不会执行了,这样的话会很危险。


我们可以先看看_objc_msgForward是如何进行转发的。

我们先来发送一个错误的消息,来看看整个消息转发是如何进行的。

call (void)instrumentObjcMessageSends(YES)

也就是说消息转发主要就是做了以下几件事:

1:调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。

2:调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。

3:调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。

4:调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。

5:调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

具体过程如下图所示:

实际应用

JSPatch中用到了_objc_msgForward进行消息转发。

利用OC的消息转发机制实现多重代理

参考

Objective-C 消息发送与转发机制原理

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注