Object-c消息之运行时动态绑定
在Objective-C中,message在执行阶段绑定,转换成对objc_msgSend方法的调用。objc_msgSend方法含两个必要参数:receiver、方法名(即:selector)。例如有一个Person类对象p且有一个实例方法eat,那么 [p eat] 会转换成类似于 objc_msgSend(p, “eat”) 之类的调用。第一个参数是Person对象p,第二个变量是eat方法对应的SEL方法描述符。
举例如下:eat的实例方法中的[self description]转换成了objc_msgSend方式。
-(void) eat { NSLog(@"%@ eat", [self description]); } static void_I_Person_eat(Person * self, SEL _cmd) { NSLog((NSString*)&__NSConstantStringImpl__var_folders_rv_c6jfnmks33g6pf286rv8yt540000gr_T_Person_oFNThV_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("description"))); }
{{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_eat}。
根据receiver查找SEL对应的方法实现代码,其实就是实现代码的函数指针,即_I_Person_eat。具体流程是首先根据receiver的isa找到receiver的类对象,然后在类对象的方法列表中根据SEL(“eat”)查找对应的方法实现,如果找不到继续向父类的类对象中寻找,以此类推,直到找到NSObject,找不到最后则抛出unKnownSelector异常。
1. 调用该实现,并将一系列参数传递过去
2. 将该实现的返回值作为自己的返回值,返回之。
延伸:
如果现在有5层继承层次,第5层需要调用第2层提供的方法,那么每一次调用此方法都需要一步一步的向第2层查找,那程序的效率如何保证呢?对于这样的情况,苹果人员不可能不想到这种情况,思考一下组成原理学习的cpu cache机制,应该就有所启发了。由于程序的局部性原理,已经调用的函数还有可能会被再次调用。因此将已经调用的函数存储一个SEL与函数实现的映射,那么下次调用就可以迅速的从cache中得到而不用逐层向上层查找,OC确实就是这样做的。
比较官方的说法是:为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找函数表前,消息发送系统首先检查receiver对象的缓存。 如果找到即调用,没有找到则逐层向上层寻找,找到后加入到缓存中。下面是一个从子类到父类的查找函数实现的流程图: