iOS小知识之方法的本质,SEL、IMP及两者关系

1,879 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

方法的本质

方法的本质其实就是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
    • 找到具体的内容-方法的具体实现

扩展

方法的快速查找流程是用汇编实现的,原因在于

  • 快速查找流程,即:方法缓存查找,目的就是提升效率。使用汇编代码最接近机器语言,可以最大程度优化存储空间与执行时间
  • 汇编代码对于动态参数、可变参数有更好的支持

二分查找法:

  • 查找过程:表中方法编号按升序排列,将表中间位置记录的方法编号与将要查找的方法编号比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果查找的方法编号大于中间位置记录的方法编号,则进一步查找后一子表,否则进一步查找前一子表。重复以上过程,直到找到满足条件的记录,此时查找成功。或直到子表不存在为止,此时查找不成功。