iOS中Runtime理论知识过一遍
一、Runtime简介
Runtime其实就是一套API,是一套由C、C++、汇编语言一起写成的API,给OC提供运行时。
Runtime是OC的底层,采用的是C、C++、汇编语言为OC语言提供运行时环境的支持。
Runtime System Library是用C、C++、汇编语言写的一个代码库,通过编译(Compiler)之后,生成的就是Runtime API和框架与服务,然后再供OC代码调用。
不管怎么说,关于Runtime的两篇官方文章是需要自己学习的:
1》Objective-C Runtime Programming Guide(传送门)
2》Objective-C Runtime Reference(传送门)
与运行时相对应的是编译时,编译时就是编译的时期,编译时主要的任务就是将源代码翻译一下,就是将高级语言(比如OC、Swift、Java等)编译成相应的机器语言,最终形成二进制可执行文件。然后进入到运行时,代码跑起来会被装载到内存中,这个时候代码就变“活”了,这个时候就需要一种东西来支持运行时需要的功能,这个东西就是runtime。
Runtime发展以来,一共有两个版本,一个是Legacy版本(早期版本),另一个是Modern版本(现行版本)。早期版本对应的编程接口是OC 1.0 ,现行版本对应的编程接口是OC 2.0 。早期版本用于OC 1.0,32位的Mac OS X 的平台上,现行版本用于iPhone程序和Mac OS X v10.5 及以后的系统中的64位程序。
OC程序有三种途径和运行时系统交互:1)通过OC源代码。2)通过Foundation框架中NSObject的方法。3)通过调用运行时系统给我们提供的API接口。
通过在项目运行时打断点的方式是无法调到runtime实现方法中去的,如果想跟踪runtime中方法的跳转,需要在Apple Open Source下载runtime的源码到项目中。本文使用的是10.14系统中的objc4-750版本的源代码。然后也可以去github上找相关的源代码(注释版)。
关于运行时一个很简单的认识就是,在代码中调用[obj run]时,如果run方法只是声明了,但是没实现的情况下。在command+B(编译)的时候并不会出现错误,只有在command+R(运行)的时候才会显示出错误。也就是说,方法的检测是在运行期进行的。
通过clang可以直接对OC文件进行编译,首先要cd到该OC源文件的所在文件件,比如这个OC源文件是main.m,输入的终端命令行为;
clang -rewrite-objc main.m -o main.cpp
编译之后会发现有很多的警告,通过下面这种方式编译,警告会少很多:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o testMain.c++
通过对一个简单的OC代码编译为c++代码:
Person *person = [[Person alloc] init];
[person run];
编译为C++后为:
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("run"));
由此可知,1)在OC中任何方法的本质就是发送消息objc_msgSend。2)通过下面截图中显示的Person的定义,可知OC对象的本质就是结构体。
然后消息的组成是:消息接受者、方法编号。
消息的接受者就是:
((void (*)(id, SEL))(void *)objc_msgSend)((id)person
方法编号:
sel_registerName("run"))
也就是说,
person对象调用run方法的模型模型就是:
void runIMP(id self, SEL_cmd) { }
IMP值的是函数实现的指针,IMP是通过sel找到的。
上面的
sel_registerName("run")
或者看起来比较陌生,其实它和@selector(run)是一个意思。
上面提到的是向对象发送消息,那么类方法是怎样的编译底层呢,向父类发送实例方法消息和向父类发送类方法消息又是怎样的呢?
// 类方法编译底层 id cls = [Person class]; void *pointA = &cls; [(__bridge id)pointA walk]; objc_msgSend(objc_getClass("Person"), sel_registerName("run"));
、
// 向父类发消息(对象方法) struct objc_super mySuper; mySuper.receiver = s; mySuper.super_class = class_getSuperclass([s class]); objc_msgSendSuper(&mySuper, @selector(run));
、
// 向父类发消息(类方法) struct objc_super myClassSuper; myClassSuper.receiver = [s class]; myClassSuper.super_class = class_getSuperclass(object_getClass([s class])); objc_msgSendSuper(&myClassSuper, sel_registerName("run"));
objc_msgSend发送消息有两种方式,一种是快速的方式,实现的方式是用缓存中的汇编cache_t,找相应的IMP哈希表,如果没有找到就通过另外一个复杂的过程,也就是第二种方式,慢速的方式,通过C语言慢慢地找,找到后会把这个IMP放到缓存中,
-----未完待续,2021年6月14日