“好记性不如烂笔头”,虽然我不是用的笔。但是敲一遍对我来说效果肯定比单纯的看一遍效果更好!!!
在对象上调用方法是OC中经常使用的功能,用OC的术语来说这叫做“传递消息”。消息有“名称(name)”和“选择子(selector)”,可以接受参数,也可以返回值。
OC是C的超集,C语言使用“静态绑定”,也就是说在编译期就能决定运行时所应调用的函数。如下代码:
如果不考虑内联(inline),那么编译器在编译代码的时候就已经知道程序中有printHello和printGoodbye这两个函数了,于是会直接生成调用这些函数的指令。而函数地址实际上是硬编码在指令之中的。若是将刚才那段代码写成下面这样,就得使用“动态绑定”了。
编译器在这种情况下生成的指令和图1中的不同。
在OC中如果向某个对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C函数,然而对象受到消息之后,究竟该调用哪个方法则完全在运行期决定,甚至可以在程序运行时改变,这些特性使得OC成为了一门真正的动态语言。
在OC中给对象发送消息可以这样来写:
someObj是接收者(receiver),messageName叫做选择子(selector),选择子和参数合起来称为“消息(message)”。编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend,其原型如下:
这是个参数可变的函数,能接受两个和两个以上的参数,第一个参数代表接收者,第二个参数是选择子(SEL是选择子的类型),后续的参数就是消息中的那些参数,其顺序不变。编译器会把图3中的消息转换成如下函数:
objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”,如果能找到与选择子名称相符的方法,就调至其实现代码。如果找不到就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终都找不到,那就执行“消息转发”操作。
当找到相符的方法之后,objc_msgSend会将匹配结果缓存在“快速映射表”里,每个类都会有这么一块缓存,如果稍后还向该类发送此消息,那么执行起来就会很快了。
上面所叙述的只是部分消息的调用过程,其实还有一些其他的“边界情况”,这时候需要交由OC运行环境中的另外一些函数来处理:
1、objc_msgSend_stret:如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。假如无法容纳的时候(比如结构体太大),那么由另一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。
2、objc_msgSend_fpret:如果消息返回的是浮点数....。书上说这个函数是为了处理x86等架构CPU中某些令人稍觉惊讶的奇怪状况。
3、objc_msgSendSuper:给超类发消息,如[super message:paramater],那么就需要此函数来处理
猜测:stret->struct return、fpret->float point return
刚才提到,objc_msgSend等函数一旦找到应该调用的方法实现之后,就会“跳转过去”。之所以能这样做,是因为OC对象的每个方法可以视为简单的C函数,其原型如下:
注:书中说“真正的函数名和上面写的可能不太一样,笔者用类(Class)和选择子(selector)来命名是想解释其工作原理”
每个类都有一张表格,表格里面存的都是指针,每个指针都会指向这种函数,而选择子的名称则是查表时候所使用的“键”。objc_messageSend等函数正是通过这张表格来寻找应该执行的方法并跳转到其实现的。图6中原型的样子和objc_messageSend很像,这不是巧合,而且为了利用“尾调用优化”技术,令“跳转到方法的实现”更加简单一些。