概念理解
编绎时
顾名思义就是正在编绎的时候;编绎就是编绎器把源代码翻译成机器能识别的代码;包括语法分析和词法分析,检查代码是否有语法错误。如果编绎时发现了代码有语法错误就会报错,这个错误就叫编绎时错误。编绎过程过程中类型检查也叫编绎时类型检查或静态类型检查。所以编绎其实就是把代码过一遍,看看有没有错误。
运行时
运行时就是代码跑起来了,被装载到内存中去了。运行时类型检查就不是简单的检查代码,而是在内存中执行代码。
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中查找和进入慢速查找流程。