美文网首页iOS学习IOS程序员
iOS 消息发送:挂羊头卖狗肉

iOS 消息发送:挂羊头卖狗肉

作者: Allan_野草 | 来源:发表于2016-12-02 11:23 被阅读455次

片头

像[rev message],就是向对象rev发送一条为叫message的消息。
OC的世界是很神奇的,不到程序运行,都不知道它最终会有执行什么东西。
为什么这么说?

原理

消息发送是基于NSObject类的一套动态函数调用机制,由runtime库提供实现。
编译器会把你写的代码转换为相应的C静态函数。
[rev message]的message,会被编译成一条C函数message=>c_message。对象rev调用message方法,就是去找message的C函数。

举个例子:

类Person有一条say:方法

@implememtation Person
- (void)say:(NSString *)something{
  // todo..
  NSLog(@"%@", something);
}

向Person类实例man发送say消息

Person *man= [Person new];
[man say:@"hello"];// - 控制台打印hello
编译器处理,
  • -say:方法被编译成C静态函数:
static void _I_Person_run(Person *self,SEL _cmd,NSString *something){
    // todo..
    // NSLog((NSString *)__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, @"hello")
    prinf("something");// 伪代码
}
  • [man say:@"hello"]:
objc_msgSend(man,@selector(say:),@"hello")

其中objc_msgSend是在runtime库定义的一条C系统函数,原型为 id objc_msgSend(id, SEL , ..)。
也就是说向man发送say:消息,会被编译成objc_msgSend函数调用,并向其依次传递参数(id)man,(SEL)@selector(say:) 和 (NSString *)@"hello"。

在程序到运行时,
  • 建立全局映射关系,用伪代码表示的话:
{
  "Person类":{
                @selector(say:) : &_c_say,// say编译出来的C静态函数
                @selector(run:)  : &_c_run,// 假设还有run方法
                ..
              }
  "其它类":..
  ..
}
  • 当objc_msgSend被执行时,其内部通过传递的参数@selector(say:),去找到say:方法被编译成的C静态函数的地址(IMP指针)
IMP ptr = class_getMethodImplementation([Person class], @selector(say:));
  • 并以函数指针方式完成调用,即调用的是_c_say。
ptr(man,@selector(say:),@hello);

补充

如果手动去干预映射关系,让:

// 交换映射位置
{
  @selector(say:) : &_c_run,
  @selector(run:)  : &_c_say,
  ..
}

那么,当程序运行到[man say:@hello]时,其实际执行的却是@selector(say:)对应的-run:方法的C函数。即在表现上明明是调用了-say:方法,却发现是-run:方法被调用了。老板,你挂羊头卖狗肉,货不对版!
不,请叫我“黑魔法”方法交换(method swizzle)。通俗原理就是这样了。

参考

Objective-C Runtime Programming Guide

End

相关文章

网友评论

    本文标题:iOS 消息发送:挂羊头卖狗肉

    本文链接:https://www.haomeiwen.com/subject/khyxmttx.html