objc runtime 消息发送

objective-c 函数的转换

比如有如下一个函数

1
2
3
4
5
6
7
8
9
// ASRTTModel.m
#import "ASRTTModel.h"
#import <objc/message.h>

@implementation ASRTTModel
+ (void)func1 {
printf("%s", __FUNCTION__);
}
@end

在终端运行下面代码,会生成一个ASRTTModel.cpp文件

1
clang -rewrite-objc ASRTTModel.m

在该文件中,我们会发现上面方法被转换成c函数实现

1
2
3
4
// 这是 func1 的IMP
static void _C_ASRTTModel_func1(Class self, SEL _cmd) {
printf("%s", __FUNCTION__);
}

看下IMP的定义

1
2
/// 函数指针, 用于表示对象方法的实现
typedef id (*IMP)(id, SEL, ...);

objective-c 消息的转换

如下,我们再添加一个函数调用func1

1
2
3
- (void)func4 {
[self func1];
}

在终端运行

1
clang -rewrite-objc ASRTTModel.m

我们会发现,该调用被转换成

1
2
3
static void _I_ASRTTModel_func4(ASRTTModel * self, SEL _cmd) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("func1"));
}

可以看到objective-c的消息被转换成

1
2
/// @selector("func1") 和 sel_registerName("func1") 等价
objc_msgSend(self, @selector("func1"))

objc_msgSend的定义:

1
2
3
4
5
6
7
8
9
10
///**
Sends a message with a simple return value to an instance of a class.

@param self A pointer that points to the instance of the class that is to receive the message
@param op The selector of the method that handles the message.
@param ... A variable argument list containing the arguments to the method.

@return The return value of the method.
*/
id objc_msgSend(id self, SEL op, ...);

在了解objc_msgSend之前,我们先了解下SEL
SEL是选择器,一个不透明结构体。实际上 SEL sel_registerName(const char *str) 里的实现返回的是一个字符串(char *),该字符串其实就是从objective-c角度看的方法名称,在runtime内部,每个class有一个cache,里面存着该class已经映射的SEL->IMP对(IMP其实就是指向上面转换生成的c函数的指针)。要往class添加新的SEL->IMP映射可以用如下方法

1
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

sel_registerName 里还维护了一个表,记录着所有已注册过的选择器。

这样 objc_msgSend 作用就很清晰了:在self对应class及super class中寻找op这个SEL对应的IMP,如果找到就调用该IMP并返回返回值,如果找不到会调用 _objc_msgForward(和IMP类型一样) 消息转发,这个后面再说。
objc_msgSend实现有用到汇编,这里贴上别人写的伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}

_objc_msgForward 消息转发的目的是把selector转发给能响应该selector的对象。这过程会依次执行以下方法(并不一定都执行一遍)

1.

1
2
3
+ (BOOL)resolveInstanceMethod:(SEL)sel
or
+ (BOOL)resolveClassMethod:(SEL)sel

我们可以在该方法里动态为该SEL添加IMP映射(使用 class_addMethod),然后返回YES,这样会再次运行一遍 class_getMethodImplementation 流程,找到相应的IMP并调用,并且不会再执行下面所有函数。 如果找到的还是 _objc_msgForward,那么就会执行下面函数。如果返回NO也会执行下面函数。
2.

1
- (id)forwardingTargetForSelector:(SEL)aSelector

在这个方法里我们可以返回一个能响应该aSelector的target,相当于执行了 objc_msgSend(newTarget, aSelector) ,下面方法不会继续执行。返回nil,继续执行下面方法。一般来说最好在这步就处理掉,因为在第4个函数处理掉会花更多的时间。
3.

1
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

在这个方法里我们可以为aSelector创建一个方法签名并返回,所谓签名就是用字符串对aSelector的IMP一些调用信息(返回值和参数)进行描述,比如:”v@:”,‘v’该IMP没有返回值,‘@’表示第一个参数为object,‘:’表示第二个参数为SEL,具体对应表可参考该文档。如果返回nil,runtime system 会直接调用第5个方法,抛出 unrecognized selector sent to instance 异常。

  1. 1
    - (void)forwardInvocation:(NSInvocation *)anInvocation

    anInvocation是用第3步的签名和一些该调用相关信息(SEL、返回值、参数)构造的NSInvocation对象。我们可以把该对象转发给其他可响应该SEL的对象调用。比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
    SEL aSelector = [invocation selector];

    if ([friend respondsToSelector:aSelector])
    [invocation invokeWithTarget:friend];
    else
    [super forwardInvocation:invocation];
    }

  2. 1
    - (void)doesNotRecognizeSelector:(SEL)aSelector

    该函数默认会抛出 unrecognized selector sent to instance 异常。如果重写该函数,并直接return掉,则即使是错误的调用也不会报错。

打印运行时发送的消息

有两种方式:

  1. 在代码中添加以下代码
    1
    2
    extern void instrumentObjcMessageSends(BOOL);
    (void)instrumentObjcMessageSends(YES);
  2. 断点暂停程序运行,并在在xcode控制台输入下面的命令:
    1
    call (void)instrumentObjcMessageSends(YES)
    所以的打印都在/tmp/msgSend-xxxx

为一个类动态添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//  ViewController.m

+ (void)dynamicAddMethod {
// 给该SEL取名myPrint
SEL sel = @selector(myPrint);
// class_addMethod 可以重写父类方法.
// sel为方法名,(IMP)printSomething为对应的实现
// "v@:"返回值、参数信息: Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
class_addMethod(self, sel, (IMP)printSomething, "v@:@");
}

// IMP
void printSomething(id self, SEL _cmd, id text)
{
NSLog(@"%s %s:动态添加的方法", __FUNCTION__, _cmd);
NSLog(@"%@", text);
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
/// 调用myPrint方法
[self performSelector:@selector(myPrint)];
}


+ (BOOL)resolveInstanceMethod:(SEL)sel {
BOOL rslt = [super resolveInstanceMethod:sel];
if (sel == @selector(myPrint)) {
/// 为该类添加myPrint方法
[self dynamicAddMethod];
rslt = YES;
}
return rslt;
}

打印

1
2
3
4
5
6
7
8
9
...
- RunTimeDemoViewController NSObject performSelector:withObject:
+ RunTimeDemoViewController RunTimeDemoViewController resolveInstanceMethod:
+ RunTimeDemoViewController RunTimeDemoViewController resolveInstanceMethod:
+ UIViewController NSObject resolveInstanceMethod:
+ RunTimeDemoViewController RunTimeDemoViewController dynamicAddMethod
- RunTimeDemoViewController RunTimeDemoViewController myPrint
- RunTimeDemoViewController RunTimeDemoViewController myPrint
...

以上就是我这两天看网上博客和自己实验得出的一些心得

ps: