iOS 消息发送 vs 函数调用,其本质区别

为什么调用一个方法,在OC里叫做“消息发送”,而在C/C++或者其它的开发语言中,都是叫做“函数调用”呢?

实际上,除了叫法不一样之外,在本质上也有根本性的区别的。这里面涉及到OC作为一门动态语言的特性。

下面先来看一下函数调用:

函数调用

比如,C中的函数调用

void game_start() { ... } // 举例函数

geme_start(); // 函数调用

C++中的函数调用

Class Cat {
    void sleep() { ... }
}

Cat cat = new Cat();
cat.sleep(); // 函数调用

之类的,其函数的函数地址在编译期已经确定,是固定不变的。

像这样的函数调用,是通过找到函数地址,再进行调用的。那么消息发送呢?

消息发送

一、

在OC中,要有这么一个认知,就是我们写一个方法:

@interface Cat : NSObject

@end

@implementation

- (void)eat:(id)food {

}

@end

编译器会帮我们将以上方法,类似转化为:

void c_cat_eat(id target, SEL selector, id food) {

}

也就是说,我们每写一个OC的方法,都相当于写了一个对应的C函数。

二、

接下来,我们发送一条消息:

Cat *cat = [Cat new];

[cat eat:nil]; // 发送消息

同样地,编译器会处理成:

objc_msgSend(cat, @selector(eat:), nil);

解释一下,

1、OC是C语言的超集,其本质上,调用方法也是通过找到函数地址来调用的。
2、这里的objc_msgSend,是OC中方法调用的“中枢”。在这个中枢里,通过“符号” => 找到函数地址。
那这个“符号”是什么呢?它就是OC里的方法选择器:@selector(方法名)。

总结就是:OC里发送消息,会统一走objc_msgSend,通过@selector(方法名),找到对应的函数地址,再进行调用。

另,此处的调用,是以函数指针的方式调用的。也就是我们熟悉的IMP指针。

三、

所以,两者的本质区别是:
函数调用 = 函数地址调用,
消息发送 = “符号” => 找到函数地址,再用函数地址进行调用。

实际应用

延伸一下,
#1方法交换(method swizzle) 。原理就是改变 “符号” (selector) => 函数地址(IMP指针) 的映射关系。
这里的映射关系,以键值对的方式,存储在类信息结构体里。详见
#2 nil的自动处理。C和C++中,nil->_name必崩。而nil.name却不会导致崩溃,原因就是走了“中枢”objc_msgSend,方法里面判断了当对象 = nil 就自动 return。

再说两句

另外补充一下,在OC方法里常用的 self 、_cmd,为什么 self 就能指代本实例对象呢?原因是编译器会帮我们进行替换,伪代码如:

- (void)eat:(id)food {
    self.food = food;
}

把 self 替换成传递进来的参数 target:

void c_eat(id target, SEL selector, id food) {
    target.food = food;
}

参考

官方文档:Objective-C Runtime Programming Guide

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容