runtime之objc_msgSend分析

923 阅读2分钟

概念理解

编绎时

顾名思义就是正在编绎的时候;编绎就是编绎器把源代码翻译成机器能识别的代码;包括语法分析和词法分析,检查代码是否有语法错误。如果编绎时发现了代码有语法错误就会报错,这个错误就叫编绎时错误。编绎过程过程中类型检查也叫编绎时类型检查或静态类型检查。所以编绎其实就是把代码过一遍,看看有没有错误。

运行时

运行时就是代码跑起来了,被装载到内存中去了。运行时类型检查就不是简单的检查代码,而是在内存中执行代码。

OC的runtime有两个版本,一个是早期的Legacy和现行的Modern

runtime在OC中的层次关系

OC代码->Framework & Service || Runtime Api -> Coompiler(LLVM) -> Runtime System Library Compiler层的设计是为了对编绎进行优化,提高性能,比如alloc和isKindOfClass的编绎优化;

调用runtime的三种方式

  • 1.通过OC的代码调用runtime;如自定义的sayNB方法的调用;
  • 2.OC底层Api的方法,比如isKindOfClass和isMemberOfClass;
  • 3.runtime Api,如class_getInstanceSize.

验证OC的方法调用的本质是runtime底层调用objc_msgSend

通过OC对象本质及isa结构分析我们可以知道使用clang可以将OC的代码编绎成C、C++的底层代码。下面我们就这么干

将如下代码编绎成.cpp文件 main.m文件编绎成了main.cpp文件

打开main.cpp文件,找到main函数

  • LGPerson *person = [LGPerson alloc];在底层被编绎成了LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));;
  • [person sayHello];被编绎成了((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));;

它们都调用了一个共同的函数objc_msgSend,OC 调用方法在runtime底层的本质其实就是调用objc_msgSend进行消息转发。

objc_msgSend流程分析

objc_msgSend 其实是用汇编写的,汇编语言可以更快速的被编绎器识别,所以使用汇编可以大大提高运行效率。

首先我们在OC底层原码781中全局搜索objc_msgSend

我们研究一下真机下的objc-msg-arm64.s文件

在此文件中全局搜索objc_msgSend,找到如下位置

ENTRY _objc_msgSend就是objc_msgSend的函数入口

下面我们来分析一下objc_msgSend的具体实现

通过传进来了的消息接收者(对象或类)的isa指针,找到对应的类对象,从类对象的cache中开始快速查找方法的实现

再搜索CacheLookup,找到其实现

通过以上步骤,成功的拿到了cache中存储方法缓存的buckets和最大存储量-1 = mask

如果在cache中没有找到

总结:

objc_msgSend通过实例对象或类对象的isa指针,找到类的内存地址,通过地址偏移找到cache,在cache中快速查找方法的实现,如果没找到而去methodList中查找和进入慢速查找流程。