0%

Objective-C Runtime 之一 基础介绍

本系列笔记参考 Runtime 源码为 objc4-779.1

什么是 Runtime

Runtime 是一套使用 C、C++、汇编实现的 API,调用 ObjC 方法时,实际上是执行的 Runtime 接口,最终执行的其实是 C/C++ 代码。

Runtime 可以理解为构成 ObjC 语言的一部分。ObjC 为 C 语言加上的面向对象的特性,通过 Clang 编译器和 Runtime 实现。

编译 ObjC 文件

使用 clang -rewrite-objc Hello.m 命令。

使用 Runtime

ObjC 方法调用

ObjC 方法调用本质上就是在调用 Runtime 的 API,比如使用 ObjC 编写如下代码:

1
2
Person *olivia = [[Person alloc] init];
[olivia sayHi];

使用命令 clang -rewrite-objc main.m 编译成 C++ 代码后如下:

1
2
Person *olivia = ((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)olivia, sel_registerName("sayHi"));

可以看到,ObjC 中调用方法本质上是在调用 objc_msgSend 函数:

1
2
3
4
5
// message.h

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

这个函数第一个参数就是方法的调用者(receiver),第二个参数是 SEL 类型,上面代码中使用 sel_registerName("方法名") 获得,如果方法含有参数,那么就是 objc_msgSend 中第三个以及之后的参数。其中 SEL 就是 方法选择器(selector),我们常常使用 @selector() 获得,它实际上是一个结构体指针。

1
2
3
4
// objc.h

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

sel_registerName 就是通过方法名这个字符串去获得 SEL

1
2
3
OBJC_EXPORT SEL _Nonnull
sel_registerName(const char * _Nonnull str)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

需要注意的是,这里说的方法名包含参数个数信息,不包含参数类型和返回值类型信息,也就是说,我们可以从一个 SEL 中得到这个方法的名字以及有几个参数(如 sayHi: 表示这个方法具有一个参数),但是无法知道这个方法的返回值类型和参数类型。所以 ObjC 中如果函数名相同且参数个数相同,即使返回值类型和参数类型都不同,编译器也是会报错的。

NSObject 方法

NSObject 中的方法大多都是调用的 Runtime 的 API:

1
2
3
4
5
6
7
8
9
10
11
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (BOOL)isProxy;

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (BOOL)respondsToSelector:(SEL)aSelector;

直接调用 Runtime 库函数

1
2
#import <objc/runtime.h>
#import <objc/message.h>

可以通过引入 Runtime 头文件,直接调用 Runtime 中的 API,基于 Runtime 的很多实现也是通过调用 Runtime API 完成的。

参考文献

  1. Objective-C Runtime Programming Guide
  2. Objective-C Runtime - 玉令天下的博客
  3. Objective-C 消息发送与转发机制原理 - 玉令天下的博客
  4. Objective-C 引用计数原理 - 玉令天下的博客