runtime 运行时--篇章一

运行时系统 – 篇章一

对象消息是以动态方式实现的,接收器的类型和相应的调用发法是在运行时决定的。所以多学学runtime的底层实现是必不可少的。

先来3个概念提个神:

  • 消息传递:对象之间发送和接受消息的通信模式。

  • 方法绑定接受向指定接收器发送的消息**并寻找**执行适当方法的处理过程。

  • 动态绑定
    运行程序时(而不是编译时)将消息与方法对应起来的过程
    在运行前和发消息前,消息和接收器的类型并不对应。不同的接收器,可以有相同的方法。

选择器

在OC中,选择器是一种文本字符串,用于指明调用对象或类中的那个(些)方法。(就是方法的名字)

  • SEL类型
    选择器类型(SEL)是一种特殊的OC数据类型,用于编译源码时替换选择器值的唯一标识符。相同选择器值的方法,SEL标识符一样。SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
1
2
3
/// An opaque type that represents a method selector.
// 不透明类型的方法选择器
typedef struct objc_selector *SEL;

例如:

1
SEL myMethod = @selector(myMethod:);

获取 SEL 有三个方法:

  1. @selector() 指令是在编译时构建的。
  2. NSSelectorFromStringFoundation函数,是运行时构建的
  3. sel_registerName函数,在Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器。
  • 空选择器分段
    即拥有一个以上的参数的方法声明可以拥有空参数。

上代码,就长这样:

1
sunAddNum::

这是一个SEL中的一个特殊例子,不过并不建议这么写,出错了难以定位。

举个栗子🌰:

1
2
3
4
5
6
@interface Test: NSObject
- (void)test1:(NSString *)str1 name:(NSString *)name;
- (void)test1:(NSString *)str1 :(NSString *)name;

// 调用
[self test1:@"" :@""];
  • _cmd

    SEL类型隐式参数,编译器在编译后会在每个方法中加两个隐藏的参数,其中一个便是_cmd,表示当前方法的selector。还有一个是self,表示当前对象。
    _cmdself表示当前方法调用的对象实例。

(案例请看动态方法解析)。

IMP

IMP实际上是一个函数指针,指向方法实现的首地址

runtime中定义:

1
2
3
4
5
6
7
/// 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

  • 第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
  • 第二个参数是方法选择器(selector)
  • 接下来是方法的实际参数列表。

NSObject 提供了如下两个方法:

1
2
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;

对应的实现源码:

1
2
3
4
5
6
7
8
9
10
11
12
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}
+ (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation((id)self, sel);
}
- (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation(self, sel);
}

通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。

方法签名

方法签名:定义了方法输入参数的数据类型和方法的返回值(如果存在)

编译器会将[接收器 消息]的对象信息,转换为声明中含有方法签名的C函数调用语句。

  • 编译器可以轻松的从对象消息表达式中提取选择器,接受器的类型也是在运行时确定的,那方法签名又怎么获取呢?
    编译器会根据已解析的方法声明进行猜测,一旦发现找不到方法签名就会报错。

动态方法解析

使用动态方法解析能够动态的实现方法。

  • 使用OC中的@dynamic指令,告知编译器与属性关联的方法会以动态的方式实现。
  • NSObject类中含有+(BOOL)resolveClassMethod:(SEL)sel
    + (BOOL)resolveInstanceMethod:(SEL)sel,通过重写这2个方法,可以以动态方式分别为指定的实例和类方法选择器提供实现代码

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *method = NSStringFromSelector(sel);
if ([method hasPrefix:@"addValue"]) {
class_addMethod([self class], sel, (IMP)addValue, "aaa");
return YES;
}

return [super resolveInstanceMethod:sel];
}

id addValue(id self, SEL _cmd, id value)
{
return value;
}
感谢您的阅读,本文由 Anrue 版权所有。如若转载,请注明出处:Anrue(https://github.com/anru1314/2018/12/01/iOS/runtime-运行时-篇章一/
不可描述的 iOS 锁事
runtime 运行时--篇章二