为什么调用一个方法,在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;
}