runtime 运行时--篇章一
运行时系统 – 篇章一
对象消息是以动态方式实现的,接收器的类型和相应的调用发法是在运行时决定的。所以多学学runtime的底层实现是必不可少的。
先来3个概念提个神:
消息传递:对象之间发送和接受消息的通信模式。
方法绑定:接受向指定接收器发送的消息**并寻找**执行适当方法的处理过程。
动态绑定:
运行程序时(而不是编译时)将消息与方法对应起来的过程。
在运行前和发消息前,消息和接收器的类型并不对应。不同的接收器,可以有相同的方法。
选择器
在OC中,选择器是一种文本字符串,用于指明调用对象或类中的那个(些)方法。(就是方法的名字)
- SEL类型
选择器类型(SEL)是一种特殊的OC数据类型,用于编译源码时替换选择器值的唯一标识符。相同选择器值的方法,SEL标识符一样。SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
1 | /// An opaque type that represents a method selector. |
例如:
1 | SEL myMethod = @selector(myMethod:); |
获取 SEL 有三个方法:
@selector()
指令是在编译时构建的。NSSelectorFromString
是Foundation
函数,是运行时构建的sel_registerName
函数,在Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器。
- 空选择器分段
即拥有一个以上的参数的方法声明可以拥有空参数。
上代码,就长这样:
1 | sunAddNum:: |
这是一个SEL中的一个特殊例子,不过并不建议这么写,出错了难以定位。
举个栗子🌰:
1 | @interface Test: NSObject |
_cmd
SEL类型隐式参数,编译器在编译后会在每个方法中加两个隐藏的参数,其中一个便是
_cmd
,表示当前方法的selector
。还有一个是self
,表示当前对象。_cmd
和self
表示当前方法调用的对象实例。
(案例请看动态方法解析)。
IMP
IMP实际上是一个函数指针,指向方法实现的首地址
在runtime
中定义:
1 | /// A pointer to the function of a method implementation. |
- 第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
- 第二个参数是方法选择器(selector)
- 接下来是方法的实际参数列表。
NSObject 提供了如下两个方法:
1 | - (IMP)methodForSelector:(SEL)aSelector; |
对应的实现源码:
1 | + (IMP)instanceMethodForSelector:(SEL)sel { |
通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
方法签名
方法签名:定义了方法输入参数的数据类型和方法的返回值(如果存在)
编译器会将[接收器 消息]的对象信息,转换为声明中含有方法签名的C函数调用语句。
- 编译器可以轻松的从对象消息表达式中提取选择器,接受器的类型也是在运行时确定的,那方法签名又怎么获取呢?
编译器会根据已解析的方法声明进行猜测,一旦发现找不到方法签名就会报错。
动态方法解析
使用动态方法解析能够动态的实现方法。
- 使用OC中的
@dynamic
指令,告知编译器与属性关联的方法会以动态的方式实现。 - NSObject类中含有
+(BOOL)resolveClassMethod:(SEL)sel
、+ (BOOL)resolveInstanceMethod:(SEL)sel
,通过重写这2个方法,可以以动态方式分别为指定的实例和类方法选择器提供实现代码
例如:
1 | + (BOOL)resolveInstanceMethod:(SEL)sel |
感谢您的阅读,本文由 Anrue 版权所有。如若转载,请注明出处:Anrue(https://github.com/anru1314/2018/12/01/iOS/runtime-运行时-篇章一/)