Runtime介绍

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。 Runtime就是这个运行时系统。

Runtime 基本是用C和汇编写的,OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。

你可以在 这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的 runtime 版本,这两个版本之间都在努力的保持一致。

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

Runtime消息传递

调用一个对象的方法: [obj foo]
编译器转成消息发送:objc_msgSend(obj, foo)

一个简单的demo,在main.m文件中

Person * person = [[Person alloc]init];
[person run];
复制代码

clang命令编译 clang -rewrite-objc main.m

打开编译后的main.cpp文件,一直拉到最后可以看见我们刚刚写的两行代码的编译结果

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 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"));}return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };复制代码

可以清晰的看到[person run] 被编译成了objc_msgSend(person,run),我们常说在OC中调用一个对象,就是像一个对象发送一个方法指令。

要了解objc_msgSend消息传递的原理,先来了解几个概念:

1、实例(objc_object)

在objc.h中

typedef struct objc_object *id; // 指向 objc_object 结构体的指针
复制代码
struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;  // isa指针 - 指向类对象:类对象中存储了创建一个实例的信息
};
复制代码

2、类对象(objc_class)

objc.h 中 calss 的定义

typedef struct objc_class *Class; // 类对象是一个指向 objc_class 结构体的指针
复制代码

runtime.h 中 objc_class 结构体的定义

struct objc_class {// isa指针 - 指向元类:元类存储了创建类对象以及类方法的所有信息Class _Nonnull isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__// 父类指针Class _Nullable super_class                              OBJC2_UNAVAILABLE;const char * _Nonnull name                               OBJC2_UNAVAILABLE;long version                                             OBJC2_UNAVAILABLE;long info                                                OBJC2_UNAVAILABLE;long instance_size                                       OBJC2_UNAVAILABLE;// 变量列表struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;// 方法列表struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;// 缓存struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;// 协议列表struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
复制代码

3、元类(Meta Class)

所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。 为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念。

实例中的isa指针指向类对象,类中保存了创建一个实例对象及实例方法所需的所有信息,类对象的isa指针指向元类,元类中保存了创建类对象以及类方法所需的所有信息。

基类的meta-class的isa指针是指向它自己

通过上图我们可以看出整个体系构成了一个自闭环

4、Method方法(objc_method)

runtime.h 文件中

typedef struct objc_method *Method; // 指向 objc_method 结构体的指针
复制代码
struct objc_method {SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE; // 方法名char * _Nullable method_types                            OBJC2_UNAVAILABLE; // 方法类型IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE; // 方法实习
}
复制代码

5、SEL方法(objc_selector)

objc.h 中

typedef struct objc_selector *SEL;
复制代码
@property SEL selector;
SEL 是 selector 的表示类型,
selector是方法选择器,是区分方法的ID,这个ID的数据结构是 objc_selector 结构体
复制代码
  • 源码中没有objc_selector结构体的具体定义
  • 其实就是个映射到方法的C字符串,命名规则是 className+methodName
  • 导致不能像C语言一样写重载函数,就是函数名相同,参数不同。因为select只记了方法名没有参数,所以没有办法区分不同参数的方法。

6、 IMP 指针 - 指向最终实现程序的内存地址

objc.h 中

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
复制代码

Method通过 selector 和 IMP 两个属性,实现了快速查询方法及实现

7、 类缓存(objc_cache)

基于理论:如果你在类上调用一个消息,你可能以后会再次调用该消息。

为了加快消息分发,系统会对方法和对应的地址进行缓存,放在objc_cache中,大部分常用的方法都是会被缓存起来的,Runtime系统实际上非常快,接近直接执行内存地址的程序速度。

struct objc_cache {unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;unsigned int occupied                                    OBJC2_UNAVAILABLE;Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};
复制代码

mask 可以理解为当前能达到的最大的index
occupied 被占用的槽位
buckets 用数组表示的Hash表

runtime会根据这三项找到缓存的位置、经过一些计算在 bukets数组中找到buket、每一个bucket包含一个selector和一个IMP,通过对比selector来判断是否有缓存

在回过头来看objc_msgSend 的执行流程:

首先,判断接收对象 person 是否为nil;
根据person对象的isa指针找到它的class类;
从类的缓存中找run,找到则分发
如果缓存中没有,在 class 类的 method list 找 run ;
如果 class 中没到 run,继续往它的 superClass 中找 ;直到基类;
都没有找到,报错,抛出异常;

逐行剖析objc_msgSend汇编源码文章对objc_msgSend的汇编指令进行分析,缓存详细的分析了是怎么在方法中找到缓存。

消息转发

如果在一个对象的类和父类基类中都没有找到要执行的方法,程序会crash;控制台会显示类似错误信息:unrecognized selector,消息被发送给了不能处理它的对象。

OC是一门动态语言,我们可以在运行期做一些事来让crash不发生,消息转发机制就是用来解决这个问题的,在运行期通过3分 【接盘侠】方法,给对象和消息更多的机会来完成成功的调用,而不是直接 crash。

在一个函数找不到时,OC提供了三种方式去补救:

一号接盘侠:
动态解析阶段:运行期添加方法

+(BOOL)resolveInstanceMethod:(SEL)sel   (实例方法调用)
+(BOOL)resolveClassMethod:(SEL)sel  (类方法调用)
复制代码

通过class_addMethod动态添加一个方法

二号接盘侠:
备援接受者:转发给另1个对象、改变方法时

-(id)forwardingTargetForSelector:(SEL)aSelector
复制代码

询问是否把消息转发给其他接受者处理

三号接盘侠:
完整消息转发:需要转发给多个对象时

-(void)forwardInvocation:(NSInvocation *)anInvocation
复制代码

如果都不中,调用doesNotRecognizeSelector抛出异常。

Runtime的应用

1. 方法交换

使用 method_exchangeImplementations

Method m1 = class_getClassMethod([M1 class], @selector(method1name));
Method m2 = class_getClassMethod([M2 class], @selector(method2name));
method_exchangeImplementations(m1, m2);
复制代码

runtime的源码,在runtime.h中方法的声明

OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
复制代码

objc-runtime-new.mm 文件中方法的实现,可以看到核心的代码实现,是交换了方法 m1 和 m2 的imp指针,所以当我们调用方法 m1 时,实际调用的是 m2 的imp,也就实现了方法的交换。这也能体现 OC 运行时语言的特点。

void method_exchangeImplementations(Method m1, Method m2)
{if (!m1  ||  !m2) return;rwlock_writer_t lock(runtimeLock);IMP m1_imp = m1->imp;m1->imp = m2->imp;m2->imp = m1_imp;// RR/AWZ updates are slow because class is unknown// Cache updates are slow because class is unknown// fixme build list of classes whose Methods are known externally?flushCaches(nil);updateCustomRR_AWZ(nil, m1);updateCustomRR_AWZ(nil, m2);
}
复制代码

2. 为category新增属性

我们都知道category中不能新增属性,准确的说是只能声明属性,而不能为我们增加属性的实现。category的实现原理,以及为什么不能新增属性,请移步这里有详细的介绍。

3. 其他

1、动态的添加一个类
KVO的实现就是利用runtime动态的添加类,系统是在程序运行的时候根据你要监听的类,动态添加一个新类继承自该类,然后重写原类的setter方法并在里面通知observer的;

2、通过 Runtime 获取一个类的所有属性
YYModel 等数据解析的框架都有用到,获取类的多有属性,属性名称,属性类型,利用递归的方式和 json 数据一一赋值;

3、动态变量控制,动态增加方法

4、自动归档和解档

5、插件开发
XCode官方不支持插件开发,通过头文件方法名猜测方法的作用,swizzle 这些方法,插入自己的代码实现插件逻辑。

6、JSPatch 热更新,其根本原理都是利用OC的动态语言特性去动态修改类的方法实现。

小结

runtime的应用还有很多,没一个点深入研究都是一个 topc,大家有兴趣和时间的时候可以逐一去研究其中的原理和实现。总之,runtime 的应用,就是利用 OC 动态语言的特性,在运行时做一些 ‘手脚’,去完成一些功能。

转载于:https://juejin.im/post/5b5fd65d5188251b1b44a594

Runtime知识点整理相关推荐

  1. iOS开发--Runtime知识点整理

    1.Runtime简介 因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时.也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译 ...

  2. java基础知识点整理一

    java基础知识点整理一 引言 '''突然发觉任何一门语言的基础知识部分,都比较杂一些.如果个人经过梳理之后,知识体系系统化,可以让基础更加牢靠一些.但是还是会有一些遗忘.所以,我想把一些比较重要但是 ...

  3. 大数据——Flink 知识点整理

    目录 1. Flink 的特点 2. Flink 和 SparkStreaming 的对比 3. Flink 和 Blink.Alink之间的关系 4. JobManager 和 TaskManage ...

  4. 06-JAVA面试核心知识点整理(时间较多的同学全面复习)

    JVM (1) 基本概念: JVM是可运行Java代码的假想计算机 ,包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收,堆 和 一个存储方法域.JVM 是运行在操作系统之上的,它与硬件没有直接的 ...

  5. Linux - 易错知识点整理(待更新)

    Linux - 易错知识点整理(待更新) 本文根据CSDN Linux进阶技能树整理的易错知识点(带练),参考资料Linux常用命令大全(非常全!!!),Linux面试题(2020最新版)(带问/练) ...

  6. Java基础知识点整理(2022年最新版)

    看了网上很多关于Java基础知识点整理的文章,但是感觉都不是很好,要么不全面,要么不准确,要么排版太乱了,所以今天整理了一份Java基础知识点整理(2022年最新版),希望对大家有帮助哈~ 由于本文篇 ...

  7. Java面试手册V2.0+突击V3.0知识点整理(一) 附封面图片

    虚静出内功 1. Java语言三大特性 1. 封装: 属性用来描述同一类事物的特征,方法可描述一类事物可做的操作 封装就是把属于同一类事物的共性(包括属性和方法)归到一个类中,以方便使用 概念: 封装 ...

  8. 知识点整理,Java基础面试题(一)

    写在前面 整理了一些互联网大厂的面试题,这些面试题经常会被问到,也是作为Java工程师需要掌握的一些知识点,毕竟理论和实践的结合,才是王道,分片整理,每天嗑些知识点,快乐每一天,如果对你有帮助,记得点 ...

  9. 大数据——Java 知识点整理

    1. JDK 和 JRE 有什么区别? JDK:Java Development Kit 的简称,java开发工具包,提供了java的开发环境和运行环境. JRE:Java Runtime Envir ...

最新文章

  1. Flickr.net傻瓜教程(二)
  2. scrollview中嵌套listview产生冲突问题
  3. XtraBackup全备与增量备份
  4. 实现pv uv统计_聊聊前端监控(二)--行为监控的技术实现
  5. 按照顺序执行_问一个多线程的问题:如何才能保证线程有序执行?
  6. ASP.NET Web API 配置 JSONP
  7. NUC1421 时间日期格式转换【日期计算】
  8. java开发就业困难吗_就业困难期,他们面临着幸福的烦恼
  9. php 编译 sass,如何在Symfony 3中使用纯PHP编译SASS(scss)
  10. FP-growth算法原理解析
  11. 单片机原理及应用程序c语言版题库,单片机原理及应用(C语言版)
  12. 计算机突然无法连接网络,win7电脑突然不能上网的四种解决方案
  13. VScode启动流程
  14. 【翻译】Paparazzi: Surface Editing by way of Multi-View Image Processing
  15. 史上最猛“员工”,疯狂吐槽亿万富翁老板小扎:那么有钱,还总穿着同样的衣服!
  16. 回车符,换行符的区别
  17. wind10MySQL闪退什么密码_小编调解技术编辑应对win10系统Mysql输入密码后闪退的操作办法的解决教程...
  18. vue中如何检测身份正反面_vue iview怎么验证身份证正则
  19. 【机器学习自学笔记4】朴素贝叶斯分类器
  20. 大一_计算机专业_职业生涯规划书(可拷贝在自己空间)

热门文章

  1. html导入excel文件,使用js-xlsx简单实现一个导入excel
  2. 战队基地_走a怪凌晨四点被赶出训练基地,粉丝礼物还被战队瓜分
  3. 计算机会计应用实训,计算机会计模拟实习报告.pdf
  4. java反编译工具jadclipse_java反编译工具jad及jadclipse
  5. 复旦计算机学硕分数线,复旦大学2019考研分数线公布,复旦复试经验请收好!...
  6. 【 C 】字符串查找基础笔记
  7. CodeOne 主题演讲:Java,未来已来
  8. [Windows编程] C++中 bool,BOOL ,VARIANT_BOOL 的区别
  9. 《iOS 8开发指南(第2版)》——第1章,第1.3节工欲善其事,必先利其器——搭建开发环境...
  10. Zookeeper的安装配置及基本开发