OC底层-runtime

runtime

  • 苹果官方说明:

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

This document looks at the NSObject class and how Objective-C programs interact with the runtime system. In particular, it examines the paradigms for dynamically loading new classes at runtime, and forwarding messages to other objects. It also provides information about how you can find information about objects while your program is running.

You should read this document to gain an understanding of how the Objective-C runtime system works and how you can take advantage of it. Typically, though, there should be little reason for you to need to know and understand this material to write a Cocoa application.

  • 翻译后:

Objective-C 语言将尽可能多的决策从编译时和链接时推迟到运行时。只要有可能,它就会动态地做事。这意味着该语言不仅需要编译器,还需要运行时系统来执行编译后的代码。运行时系统充当Objective-C语言的一种操作系统;这就是使语言起作用的原因。

本文档着眼于NSObject类以及 Objective-C 程序如何与运行时系统交互。特别是,它检查了在运行时动态加载新类以及将消息转发到其他对象的范式。它还提供有关如何在程序运行时查找有关对象的信息的信息。

您应该阅读本文档以了解 Objective-C 运行时系统的工作原理以及如何利用它。但是,通常情况下,您几乎没有理由需要了解和理解这些材料来编写 Cocoa 应用程序。

在iOS开发中我们经常能够听到runtime,我们都知道这是运行时,但是为什么叫运行时,它做了什么呢?这个就要从苹果的运行时机制说起了,iOS是一门动态语言,将编译时的任务延迟到了运行时。在iOS中runtime是运用非常广泛的,特别是系统底层的东西。

  • runtime的应用:

    • 给系统分类添加属性、方法
    • 方法交换
    • 获取对象的属性、私有属性
    • 字典转换模型
    • KVC、KVO
    • 归档(编码、解码)
    • 。。。

方法的本质

1、那么我们接下来看下,系统是如何进行方法的调用的呢?我们将main.m通过clang编译成main.cpp文件,然后看下main函数的内容,如下图:

image.png

2、我们发现系统是通过objc_msgSend进行的消息发送,也就是说我们进行方法调用就是进行消息发送吗?下面我们修改下代码,然后再次验证下,代码如下:

image.png

3、然后我们继续通过clang编译,然后看到结果如下:

image.png

4、从编译结果可以看到,一样的效果,那么我们是否可以直接通过调用objc_msgSend进行方法调用呢?修改代码后运行结果如下:

image.png

5、说明我们是可以直接通过这种方式调用方法,那么我们就可以得到以下结论了:

  • 我们在开发过程中可以通过以下几种方式调用方法

1、[类/类对象 类方法/对象方法]

2、使用runtime提供的api进行消息发送objc_msgSend

3、通过框架提供的api进行方法调用performSelector等等

上篇文章我们看了cache,然后分析了它的作用,但是什么时候加入缓存的呢?我们只知道这个是缓存方法的,但是并不了解什么时候进行了方法缓存,那么接下来我们开始探索这个cache的调用过程。

cache调用过程

1、既然想看调用过程,我们就想到了最简单的方式,直接添加断点查看,我们查看cache_t结构体的时候,看到有一个insert函数,那么我们就找到insert的实现函数,然后在里面添加断点,如下图:

image.png

2、然后我们在main.m中编写一段代码,如下图所示:

image.png

3、然后我们运行代码,然后走到我们的断点处,运行截图如下:

image.png

image.png

4、我们通过断点调试能够看到堆栈信息,然后通过堆栈信息,我们找到了是在log_and_fill_cache函数中调用的insert,然后我们继续往前看能够找到是在lookUpImpOrForward函数中进行调用的log_and_fill_cache函数,那么我们继续查找发现是在_lookUpImpTryCache函数中调用了lookUpImpOrForward函数,然后继续查找,发现有两个地方调用了,然后我们进行断点调试看看先走的哪个,最后结果如下图:

image.png

5、然后我们继续查看lookUpImpOrNilTryCache函数在哪调用的,然后继续全局搜索调用,然后添加断点,最后发现走的断点如下:

image.png

6、然后我们继续去看在哪调用的这个resolveInstanceMethod函数,结果如下图:

image.png

7、然后我们继续查找这个resolveMethod_locked函数的调用在哪,最后发现如下图所示:

image.png

8、到这里是不是发现很熟悉了?没错我们又回来了,那么我们接下来详细看下这个函数的实现,具体如下:

image.png

9、我们能够看到这个函数中的代码注释,意思就是newallocinit等这些方法不做缓存。然后执行了一个迭代器循环,然后会在当前类的方法列表中进行查找调用的方法Method meth = getMethodNoSuper_nolock(curClass, sel);然后判断是否有这个方法,如果找到,直接执行一个goto跑到了如图所示的地方:

image.png

10、然后先进行一个与运算判断,因为外面传入的behavior3,这块的LOOKUP_NOCACHE8,它俩进行与运算结果是0所以满足条件,进入if判断中,然后又一个if判断,但是这个判断是进不去的,它的定义如下:

image.png

11、我们可以看到只有在arm64架构也就是真机调试才会走的这个,所以这块直接走到了log_and_fill_cache这个函数,那么接下来看下这个函数:

image.png

12、然后到这里我们已经找到了cache的缓存流程。

总结

怎么说呢,底层探索有苦也有乐,苦于其乏味,乐于其探索,坚持方能开拓更大的空间,加油!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享