当前位置:   article > 正文

iOS Runtime运行机制详解

ios runtime运行机制

一直想发关于Runtime的文章,但是由于读的资料一多,对Runtime的理解又加深了一些,所以总是改来改去,导致一篇都没发出来(其实是因为懒)。

最近又有小伙伴问我,我也只好再次补老账了。。。

---------啥是OC Runtime----------

OC Runtime呢,其实就是一个Runtime库

(那啥是Runtime库呢?给你个小链接:https://www.cnblogs.com/allencelee/p/7573627.html

它主要以C和汇编语言为基础,使用面向对象的OC来编写。这意味着,它可以加载类,也可以对消息进行转发、分发等。总而言之,OC Runtime为OC这门面向对象的语言提供了基础性的结构支持。

Runtime函数

**Objective-C是基于C语言,并加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象(动态创建功能),进行消息发送和转发。

**Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。

        上面说了,由于Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统 (Runtime system)来动态得创建类和对象、进行消息传递和转发。所以,理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。

         Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。虽然有一些方法构成了NSObject类的基础,但是你在写 Objc 代码时一般不会直接用到这些函数的,除非是写一些 Objc 与其他语言的桥接或是底层的debug工作。在Objective-C Runtime Reference 中有对 Runtime 函数的详细文档。

了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。

Runtime其实有两个版本: “modern(现代版)” 和 “legacy(遗留版)”。我们现在用的 Objective-C 2.0采用的是现行 (Modern) 版的 Runtime系统,只能运行在 iOS和 macOS 10.5之后的 64位程序中。而 macOS较老的32位程序仍采用 Objective-C 1中的(早期)Legacy版本的 Runtime系统比如(iPhone4s经典小机机)。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

Runtime基本是用 C汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的 runtime版本,这两个版本之间都在努力的保持一致。

平时的业务中主要是使用官方Api,解决我们框架性的需求。

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OCC语言的过渡就是由runtime来实现的(即语言转化功能)。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变成为面向过程的结构体。

Runtime消息传递

一个对象方法如:[obj foo],编译器转成消息发送:objc_msgSend(obj, foo)Runtime时执行的流程是这样的:

  • 首先,通过objisa指针找到它的 class(类);
  • 在 class的 method list找 foo;
  • 如果 class中没到 foo,继续往它的 superclass中找 ;
  • 一旦找到 foo这个函数,就去执行它的实现IMP

(isa  指针详解:深入理解iOS开发中的isa指针 - 简书,这篇博客比较绕,看看头部的讲解就行了)

但这种实现有个问题,效率低。但一个class往往只有 20%的函数会被经常调用,可能占总调用次数的 80%。每个消息都需要遍历一次objc_method_list并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情 - 再找到foo之后,把foomethod_name作为keymethod_imp作为value给存起来。当再次收到foo消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。从前面的源代码可以看到objc_cache是存在objc_class结构体中的。

objec_msgSend的方法定义如下:

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

简单来讲:“runtime方法调用的流程是:当要调用一个方法时,先不去Class的方法列表中查找,而是先去找cache_t cache 。当系统调用过一个方法后,会将其实现IMP和key存放到cache中,因为理论上一个方法调用过后,被再次调用的概率很大。这个可以参考《Objective-C高级编程:iOS与OS X 多线程和内存管理》P24页 专栏:提高调用Objective-C 方法的速度: IMP caching(指针缓存方法)-(IMP:一个函数指针,保存了方法的地址)” (关于啥是IMP,可参考:iOS中的SEl和IMP到底是什么 - 简书

“如果cache_t cache中没有这个方法,在执行上面的Runtime流程。”

关于方法调用,我们将会在别的章节描述。
可以看出:其优势是通过缓存调用,方法运行更快, IMP caching就是一种高速缓存方法。重点是 指针这些东东。

那消息传递是怎么实现的呢?我们看看对象(object),类(class),方法(method)这几个的结构体:

  1. //对象
  2. struct objc_object {
  3. Class isa OBJC_ISA_AVAILABILITY;
  4. };
  5. //
  6. struct objc_class {
  7. Class isa OBJC_ISA_AVAILABILITY;
  8. #if !__OBJC2__
  9. Class super_class OBJC2_UNAVAILABLE;
  10. const char *name OBJC2_UNAVAILABLE;
  11. long version OBJC2_UNAVAILABLE;
  12. long info OBJC2_UNAVAILABLE;
  13. long instance_size OBJC2_UNAVAILABLE;
  14. struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
  15. struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
  16. struct objc_cache *cache OBJC2_UNAVAILABLE;
  17. struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
  18. #endif
  19. } OBJC2_UNAVAILABLE;
  20. //方法列表
  21. struct objc_method_list {
  22. struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
  23. int method_count OBJC2_UNAVAILABLE;
  24. #ifdef __LP64__
  25. int space OBJC2_UNAVAILABLE;
  26. #endif
  27. /* variable length structure */
  28. struct objc_method method_list[1] OBJC2_UNAVAILABLE;
  29. } OBJC2_UNAVAILABLE;
  30. //方法
  31. struct objc_method {
  32. SEL method_name OBJC2_UNAVAILABLE;
  33. char *method_types OBJC2_UNAVAILABLE;
  34. IMP method_imp OBJC2_UNAVAILABLE;
  35. }
  1. 系统首先找到消息的接收对象,然后通过对象的isa指针找到它的类。
  2. 在它的类中查找method_list,是否有selector方法。
  3. 没有则查找父类的method_list
  4. 找到对应的method,执行它的IMP()
    1. IMP是”implementation”的缩写,存储的是OC实现方法代码块的地址,是一个函数指针,它在Runtime中的定义如下:
    2. /// A pointer to the function of a method implementation.
    3. typedef void (IMP)(void / id, SEL, ... */ );
    4. 参考链接:https://www.jianshu.com/p/0a92d39a2114
  5. 转发IMPreturn值。
  6. 如果还是没有,找不到该方法就进行消息转发(3次拯救)

关于后续使用场景,我们下一讲在说。

参考链接:Objective-C runtime机制(1)——基本数据结构:objc_object & objc_class_objc_object的数据结构_slunlun的博客-CSDN博客

参考链接:iOS Runtime详解 - 简书

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/428272
推荐阅读
相关标签
  

闽ICP备14008679号