objective-c 函数的转换
比如有如下一个函数
1 | // ASRTTModel.m |
在终端运行下面代码,会生成一个ASRTTModel.cpp文件
1 | clang -rewrite-objc ASRTTModel.m |
在该文件中,我们会发现上面方法被转换成c函数实现
1 | // 这是 func1 的IMP |
看下IMP的定义
1 | /// 函数指针, 用于表示对象方法的实现 |
objective-c 消息的转换
如下,我们再添加一个函数调用func1
1 | - (void)func4 { |
在终端运行
1 | clang -rewrite-objc ASRTTModel.m |
我们会发现,该调用被转换成
1 | static void _I_ASRTTModel_func4(ASRTTModel * self, SEL _cmd) { |
可以看到objective-c的消息被转换成
1 | /// @selector("func1") 和 sel_registerName("func1") 等价 |
objc_msgSend的定义:
1 | ///** |
在了解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 | id objc_msgSend(id self, SEL op, ...) { |
_objc_msgForward
消息转发的目的是把selector转发给能响应该selector的对象。这过程会依次执行以下方法(并不一定都执行一遍)
1.
1 | + (BOOL)resolveInstanceMethod:(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
- (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];
}1
- (void)doesNotRecognizeSelector:(SEL)aSelector
该函数默认会抛出
unrecognized selector sent to instance
异常。如果重写该函数,并直接return掉,则即使是错误的调用也不会报错。
打印运行时发送的消息
有两种方式:
- 在代码中添加以下代码
1
2extern void instrumentObjcMessageSends(BOOL);
(void)instrumentObjcMessageSends(YES); - 断点暂停程序运行,并在在xcode控制台输入下面的命令:所以的打印都在/tmp/msgSend-xxxx
1
call (void)instrumentObjcMessageSends(YES)
为一个类动态添加方法
1 | // ViewController.m |
打印
1 | ... |
以上就是我这两天看网上博客和自己实验得出的一些心得
ps: