runtime 如何通过 selector 找到对应的 IMP
2020-03-12 本文已影响0人
EmulatingStep
实例方法
和类方法
的存储位置:
- 实例方法:每个实例的
isa
指针指向着对应类对象,而每一个类对象中都一个对象方法列表。- 类方法:每个类对象的
isa
指针都指向着对应的元类对象,而每一个元类对象中都有一个类方法列表。
- 当我们发送一个消息给一个
NSObject
对象时,这条消息会在对象的类对象方法列表里查找。- 当我们发送一个消息给一个类时,这条消息会在类的
meta class
对象的方法列表里查找。
- 方法列表中记录着一个个方法实体(结构体
Method
),实体中包含方法的名称,方法实现,以及参数类型。 - 其实
selector
本质就是C字符串的方法名称(见下面分析),通过这个方法名称就可以在方法列表中找到方法实体,进一步找到对应的方法实现。
Selector,Method 和 IMP 的区别与联系
Selector
定义:
typedef struct objc_selector *SEL
翻译成中文叫做选择子或者选择器,选择子代表方法在Runtime
期间的标识符。为SEL
类型,虽然SEL
是objc_selector
结构体指针,但实际上它只是一个C字符串
。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到Objective-C
的Runtime
运行系统。
常见的有两种方式来获取/创建选择子:
SEL selA = @selector(setString:);
SEL selB = sel_registerName("setString:");
在控制台测试:
(lldb) p selA
(SEL) $1 = "setString:"
(lldb) p selB
(SEL) $2 = "setString:"
两者打印出来的都是字符串。
我们从sel_getName()
方法的源码可以看出SEL
和const char *
是可以相互转化的:
const char *sel_getName(SEL sel) {
return sel ? (const char *)sel : "<null selector>";
}
如果将selA
和selB
强转为为const char *
:
(lldb) p (const char *)$2
(const char *) $3 = 0x00007fff9a3794b5 "setString:"
(lldb) p (const char *)$1
(const char *) $4 = 0x00007fff9a3794b5 "setString:"
(lldb)
上面的结果可以看出,selA
和selB
指向相同的地址,代表同一个字符串。
如果某个类实现了setString:
方法,那么以上两个选择子传入 respondsToSelector()
方法返回的结果都将为YES
。
不同类中相同名字的方法所对应的方法选择子是相同的。
Implementation(IMP):
定义:
typedef id (*IMP)(id, SEL, ...)
代表函数指针,即函数执行的入口。该函数使用标准的C
调用。
- 第一个参数指向
self
(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者; - 第二个参数代表方法的选择子;
-
...
代表可选参数; - 前面的
id
代表返回值。
Method
定义:
typedef struct objc_method *Method
Method 对开发者来说是一种不透明的类型,它是一个objc_method
结构体指针,objc_method
的定义为:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name; // 方法选择器。
char *method_types; // 存储着方法的参数类型和返回值类型。
IMP method_imp; // 函数指针。
}
- 方法名
method_name
类型为SEL
,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同; - 方法类
method_types
是个char
指针,其实存储着方法的参数类型和返回值类型,即是Type Encoding
编码; -
method_imp
指向方法的实现,本质上是一个函数的指针,就是前面讲到的Implementation
。
Selector
,Method
,IMP
它们之间的关系可以这么解释:
一个类(Class
)持有一个方法列表,在运行期分发消息,表中的每一个实体代表一个方法(Method
),它的名字叫做选择子(SEL
),对应着一种方法实现(IMP
)。