小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
方法的本质
方法的本质其实就是objc_msgSend
消息发送,objc_msgSend的参数有
- 消息接收者
- 消息主体(SEL + 参数) 我们可以通过下面的方法验证,在main函数中,写入以下代码:
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person say:@"NB"];
生成cpp文件,可以看到底层代码为
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_d8842a_mi_3);
- sel_registerName为@selector()的底层实现
- 如果子类中没有方法,会向父类查找即调用
objc_msgSendSuper
,向父类发送消息
发送消息的主要流程如下:
- 快速查找:
objc_msgSend
查找cache_t
缓存消息 - 慢速查找:递归自己和父类查找方法
lookUpImpOrForward
- 查找不到消息,进行动态方法解析:
resolveInstanceMethod
- 消息快速转发:
forwardingTargetForSeletor
- 消息慢速转发:消息签名
methodSignatureForSelector
和分发forwardInvocation
- 最终仍为找到消息:程序crash,报经典错误消息
unrecognized selector sent to instance xxx
SEL、IMP及两者关系
- SEL是方法编号,即方法名称,在dyld加载镜像时,通过read_image方法加载到内存的表中了。
- IMP是函数实现指针,找IMP就是找函数的过程
- 两者关系:sel相当于书本的目录标题,imp就是书本的页码。查找具体的函数就是想看这本书里面具体篇章的内容:
- 我们首先要找到想看的标题们也就是title-sel
- 然后根据目录找到对应的页码-imp
- 找到具体的内容-方法的具体实现
扩展
方法的快速查找流程是用汇编实现的,原因在于
- 快速查找流程,即:方法缓存查找,目的就是提升效率。使用汇编代码最接近机器语言,可以最大程度优化存储空间与执行时间
- 汇编代码对于动态参数、可变参数有更好的支持
二分查找法:
- 查找过程:表中方法编号按升序排列,将表中间位置记录的方法编号与将要查找的方法编号比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果查找的方法编号大于中间位置记录的方法编号,则进一步查找后一子表,否则进一步查找前一子表。重复以上过程,直到找到满足条件的记录,此时查找成功。或直到子表不存在为止,此时查找不成功。