Runtime是C、C++、汇编写的一套API,特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,我们从以下几个方法去探究以下Runtime的实现机制:
- Runtime介绍
- Runtime消息传递
- Runtime消息转发
- Runtime应用
Runtime介绍
OC是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态创建类和对象,进行消息传递和转发。理解Runtime机制可以帮助更好的了解这个语言,了解底层,帮助我们可以更好的使用上层提供的方法。
- 编译:编译就是编译器把源代码翻译成机器能够识别的代码
- 运行:代码跑起来,被装载到内存中去
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类,转变为面向过程的结构体。
常见结构体介绍
我们知道对象本身是一个结构体,类也是,这里先来提前了解一下相关的概念:
- 类对象(objc_class)
OC类是由Class类型表示的,它实际上是一个指向objc_class
结构体的指针:
typedef struct objc_class *Class;
在objc/runtime.h
中objc_class
结构体的定义如下:
struct objc_class : objc_object {
// Class ISA;
Class superclass; // 父类
cache_t cache; // 缓存
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// 存储方法列表 协议列表等
class_rw_t *data() {
return bits.data();
}
...
}
类对象就是一个结构体struct objc_class
,结构体中包含的信息就是这些,这个结构体存放的数据被称为元数据(metadata
)。
这个结构体继承objc_object,所以第一个成员变量也是isa
指针,说明Class本身其实也是一个对象,称为类对象,类对象在编译器产生,用于创建实例对象。
cache_t cache
存放着类的方法缓存,cache_t是增量扩展的哈希表结构。哈希表内部存储的是bucket_t
,bucket_t
中存储的是SEL和IMP的键值对。
- 如果是有序方法列表,采用二分查找
- 如果是无序方法列表,直接遍历查找
cache_t结构体:
// 缓存曾今调用过的方法,提高查找速度
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; // 散列表的长度 - 1
mask_t _occupied; // 已经缓存的方法数量,散列表的长度是大于已经缓存的数量的
....
}
struct bucket_t {
private:
cache_key_t _key; // SEL作为key @selector()
IMP _imp; // 函数的内存地址
...
}
散列表查找过程,在objc-cache.mm
文件中:
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
data()
里面存放着方法列表,属性列表,协议列表等信息,是class_rw_t
结构体指针类型:
// 可读可写
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
指向只读的结构体,存放类的初始信息
const class_ro_t *ro; //
这三个都是二维数组,是可读可写的,包含了类的初始内容,分类的内容。
methods中,存储 method_list_t -> method_t
这三个二维数组中的数据有一部分是从class_ro_t中合并过来的
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
}
const class_ro_t *ro;
存储了当前类在编译期就已经确定的属性、方法以及遵循的协议:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
我们知道实例对象的isa指针指向该对象所属的类,那么类对象的isa指针指向谁呢?就是元类
。
- 实例(objc_object)
/// Represents an instance of a class.
struct objc_object {
private:
isa_t isa;
....
}
/// A pointer to an instance of a class.
typedef struct objc_object *id;
通过上面我们很容易看到,对象就是一个指向类对象生成的实例对象的指针,类对象的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?
- isa源码分析
isa_t是共用体,简化结构如下:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0 代表普通指针 1:表示优化过的,可以存储更多的信息
uintptr_t has_assoc : 1; // 是否设置过关联对象
uintptr_t has_cxx_dtor : 1; // 是否有C++的析构函数
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6; // 用于在调试时分析对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否有被弱引用之香果
uintptr_t deallocating : 1; // 是否正在释放
uintptr_t has_sidetable_rc : 1; // 引用计数器是否过大无法存储在ISA中,如果为1,那么引用计数器会存储在一个叫做sideTable的结构体中
uintptr_t extra_rc : 19; // 里面存储的值是引用计数器减1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
就是从类对象的isa
指针指向的结构体创建,上面我们提到过,类对象的isa
指针指向元类(metaclass
),元类中保存了创建类对象以及类方法所需的所有信息,下面可以看下isa的走位图:
通过上面这个图,我们可以看出
对象的isa指针指向所属类,类中存储着对象可以调用的方法,也就是实例方法
类对象的isa指针指向所属的元类,元类中存储着类对象可以调用的方法,类方法,但是类方法是以实例方法的形式存储在元类中
所有元类的isa指针指向根元类
根元类的父类是NSObject
NSObject的isa指针指向根元类
元类(meta class)
在上面我们多次提到了元类这个概念,元类(meta class)是一个类对象的类。
上面我们提到过,类也可以看成一个对象,就是类对象,我们给类对象发送消息(即调用类方法)。为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class
结构体,因此,就引出了meta class
的概念,元类中保存了一个创建类对象以及类方法所需的所有信息。
NSObject的元类就是根元类,也是其余类的元类的父类,根元类的isa指针还是指向自己的,根元类的父类是NSObject。
我们可以看下面获取类方法的源码:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
实际上也是获取该类所属元类的实例方法。
或者直接打印使用class_getClassMethod和class_getInstanceMethod两种方法获取到的方法的method地址进行验证。
- Method(objc_method)方法
直接上定义:
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name 方法名;
char * _Nullable method_types 方法类型;
IMP _Nonnull method_imp 方法实现;
}
这个结构体重,我们看到了SEL
和IMP
,说明SEL
和IMP
都是Method
的一部分。
- SEL(objc_selector)
定义如下:
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;
方法选择器,也是objc_msgSend
函数的第二个参数,它是selecotr
在OC
中的标识类型(Swift中是Selector
类)。selector
是方法选择器,可以理解为区分方法的ID
,而这个ID
的数据结构是SEL
:
@property SEL selector;
可以看到selector
是SEL的一个实例。
其实selector
就是个映射到方法的C
字符串,可以使用OC编译器命令@selector()
或者Runtime
的sel_registerName
函数来获得一个SEL
类型的方法选择器。
- 同一个类,selector不能重复
- 不同的类,selector可以重复
所以,我们不能跟C一样,使用函数重载,同一个类中不能拥有两个方法名相同的方法。
- IMP
/// A pointer to the function of a method implementation. 指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...);
#endif
就是指向最终实现程序的内存地址的指针
在iOS的Runtime
中,Method
通过selecotr
和IMP
,实现了快速查询方法及实现
类缓存(objc_cache)
当Objective-C
运行时,通过跟踪它的isa
指针检查对象时,它可以找到包含所有方法的方法列表,然而,可能只用到其中的一部分,每次都要查所有的,很费时间。所以类实现一个缓存,这个缓存中存储着之前访问过的方法,因为你可能以后再次调用该消息,提高方法查找性能。Category(objc_category)
Category表示一个指向分类的结构体的指针:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
分类以及其相关的探究在另一篇文章中有介绍。
Runtime消息传递 & 消息查找
比如我们给一个Person类的.h文件增加了一个方法:
- (void)play;
我们创建一个Person类的实例,去调用play方法:
[person play];
编译一下,完全没问题,运行的时候,会发现,崩溃了,在调用play方法,到产生崩溃之间,系统是做了什么,又是如何处理的呢?
首先,我们或多或少都知道,对象的方法调用,编译器都会转成消息发送objc_msgSend(obj, foo)
,Runtime时执行的流程大概是这样的:
- 通过person的isa指针,找到person所属的类,也就是Person
- 在Person这个类的 method list(方法列表)中找 play方法
- 如果Person类的方法列表没有找到foo,继续递归往它的superclass中找,一直找到NSObject的方法列表
- 如果这个过程中能找到那就直接去执行方法的实现IMP
- 如果这个过程(消息查找)没有找到,那么就会进入消息转发流程(下面会介绍)
- 如果消息转发没有处理,那么就会报错:没有找到这个方法的实现
我们这里先分析消息查找的流程,是如何通过sel
查找到对应的imp
的?
我们肯定是要从objc_msgSend
这个方法的实现入手,这个方法是由汇编和C共同完成的,这里我们主要看C的实现的部分,也能体现出方法的查找流程。
objc_msgSend 会有两种方式查找:
- 快速 汇编 在缓存中找 通过SEL找imp(哈希表),找不到就下面那个过程
- 慢速 C C++,找到了会存入缓存,没找到,另外一个复杂的过程:消息转发
objc_msgSend 是汇编写的,为什么用汇编写的?
- C不可能写一个函数,保留一个未知的参数(比如一个实例对象,运行时才知道它是什么类型的),跳转到任意的指针
- 快
- 汇编可以啊,有寄存器
首先,汇编语言,缓存中找那个imp,如果没有找到会调用 MethodTableLoopUp方法,方法列表中查找。
MethodTableLoopUp 中调用 _class_lookupMethodAndLoadCache3(核心方法)
去掉一个来搜索,查找这个_class_lookupMethodAndLoadCache3方法
这个方法我们在下面还会用到,因为它也包含了消息转发后的逻辑处理:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
next>
// cls 是一个类对象
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 再从缓存中查找
if (cache) {
// 这次也是汇编查找
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
// 判断实现
if (!cls->isRealized()) {
// 实现类 DATA里面一系列 赋值
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
// 为什么再缓存获取一次?(remap(cls) 重映射)
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 查找当前类的方法列表
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 如果找到了,就存如缓存,然后返回
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 递归查找父类的缓存 和 父类的方法列表
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 缓存赋值
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 没有找到imp,会进行动态解析,注意triedResolver,初始为NO,解析一次设置为YES了,所以只会动态解析一次
if (resolver && !triedResolver) {
runtimeLock.unlock();
// 这一步,如果处理之后,也就是进行了消息动态方法解析之后
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
// 执行完消息动态方法解析之后,会再retry,再次进入上面递归查找方法,此时如果动态方法解析中处理了,那么这一次就能被找到,然后执行了
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 如果没有找到这个方法,动态解析也没有解决,就进入消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
通过上面我们可以得出方法查找的流程是:
- 先从缓存中找
- 缓存中没有,从该类的方法列表中找
- 该类的方法列表中没有,递归从父类的缓存和父类的方法列表中找
- 如果在第2、3步找到,就会加入到方法缓存中,然后返回
Runtime消息转发
上面👆介绍了发送消息之后,方法查找的流程,如果上面那些操作都没有找到对应的方法实现,那么就会进入到消息转发的流程:
- 动态方法解析
- 备用接受者
- 完整消息转发
下面,我们一个一个进行介绍:
- 动态方法解析
首先,OC运行时会调用+resolveInstanceMethod:
或者+resolveClassMethod:
,让你有机会提供一个函数实现,如果你添加了函数并返回YES
,那运行时系统就会重新启动一次消息发送的过程。
例子如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(test:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test:)) {//如果是执行foo函数,就动态解析,指定新的IMP
class_addMethod([self class], sel, (IMP) testMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void testMethod(id obj, SEL _cmd) {
NSLog(@"Doing test");//新的test函数
}
从上面可以看到,虽然没有实现test:
这个函数,但是我们通过class_addMethod
动态添加testMethod
函数,并执行testMethod
这个函数的IMP
,也就不会报错了。
我们从Runtime源码入手,看一下这一块的源码,在上面的_class_lookupMethodAndLoadCache3
方法实现中,我们发现消息查找无果后,会进入消息动态解析的流程,调用_class_resolveMethod
这个方法:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 这个里面去查找cls的元类是否有 resolveInstanceMethod,又是递归查找,但是这一次不会再有消息动态解析,因为会产生死循环,也是因为NSObject已经实现了resolveInstanceMethod,同时也有外部传入参数resolver的控制
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//(类方法) - 元类(实例) - 根元类(实例) - NSObject (实例方法)
// 这里要体会一下根元类的父类是NSObject NSObject的类方法以实例方法的形式存储在根元类中
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
之后,调用动态方法解析的方法
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 判断cls->isa 元类 及其父类 是否实现了 SEL_resolveInstanceMethod,如果自己没有实现,会查找到NSObject
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
****这里是调用方法 SEL_resolveInstanceMethod*****
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
// 调用动态方法解析完成之后,如果用户已经处理了,给sel添加了对应的imp,此时会再次进入消息递归查找,此时就能找到了,然后可以执行了,很完美
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
看上面的代码,最终会调用SEL_resolveInstanceMethod,如果自己实现了这个方法,我们可以在这里面去动态添加那个方法了。
添加了那个方法之后,会再次递归查找本类 父类的方法列表,此时就能找到然后执行了。
- 消息转发类方法处理
我们再次需要明白:
对象方法的存储 - 类
类方法的存储 - 元类,相当于对象方法
所以类方法的查找就需要找该类所属的元类的方法列表是否有同名的对象方法。
比如Person 继承NSObject,Person 有 +walk 类方法声明,但是没有实现,NSObject+Test.m 分类里面有 -walk实例方法的实现,这样在执行[Person walk]的时候,也不会进入消息动态解析。
因为Person的+walk
类方法查找是,先查找Person所属的元类的实例方法,如果没有,查其父类,一直到根元类,也没有,再查找根元类的父类:NSObject,因为我们通过NSObject+Test.m这个分类中,添加了-walk
的方法实现,所以就找到了walk
的方法实现,可以执行。
类方法查找的时候,也会执行下面这个方法:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
此时 cls 是元类,inst 是一个类对象,在查找一个类的类方法的时候,它会查找类所属的元类,及其元类的父类,一直到根元类,最后到根元类的父类NSObject。
比如上面那个例子,我们想要找Person的+walk
这个类方法,因为类方法存储在元类里面,我们会找Person的元类,递归找到Person元类的父类,一直到根元类,都没有,此时根元类的父类是NSObject,发现有-walk
这个实例方法(我们知道类方法,在元类中以实例方法进行存储),然后就调用-walk
方法了。
NSObject的isa指向根元类,也就是NSObject的元类是根元类,所以NSObject的类方法,会存到根元类里面,以实例方法的形式。
所以类方法可以在NSObject中,以类方法或者实例方法实现。
再比如上面那个例子,如果我们在NSObject分类中 实现 +walk
类方法,不去用实例方法了,此时+walk
这个方法,会以实例方法的姿态存储到NSObject的元类(根元类)中,当Person 调用 +walk
方法查找的时候,会查找到根元类中,在根元类中找到 - walk
方法,去调用,不会再去查找NSObject是否有这个方法。
所以以上面的例子举例,NSObject分类中,实现 -walk 或者 +walk 都能调用,但是 用 +walk类方法的实现,比 -walk实例方法的实现,要少查找一步,可以再次结合下面这张图去仔细体会:
根元类的父类是NSObject NSObject的元类是根元类
,这一点挺重要。
我们继续看下类方法的动态方法解析,有什么不同:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 此时会进入到这里 cls 也就是元类
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 会走到这一步,这一步会调用NSObject的+resolveInstanceMethod:
// 它会给 cls 发送 SEL_resolveInstanceMethod 这个方法,又是方法查找
// 一直到根元类的父类,NSObject 发送 SEL_resolveInstanceMethod 消息
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
_class_resolveClassMethod实现:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 给 _class_getNonMetaClass(cls, inst) 这个对象 发送了消息 ,其实内部也是一个类对象 不是 元类
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
动态方法解析,防崩溃处理
可以给NSObject添加分类,重写
+ resolveInstanceMethod:
方法,因为不管是任何类的实例方法或者类方法,没有实现的时候,都可以走到NSObject分类中+resolveInstanceMethod: 这个方法中。备用接受者
如果动态解析没处理,进入消息转发,这个只有汇编调用,没有源码实现
如果上面那个没有新增方法,那就问问是否有别人会处理这个方法?
调用的是:
+ (id)forwardingTargetForSelector:(SEL)aSelector; // 类
- (id)forwardingTargetForSelector:(SEL)aSelector; // 实例
比如一个DDPerson
类,其中有-run
方法声明,但是没有实现,我们可以指定一个别的接收者:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"instance - forwardingTargetForSelector");
DDStudent *student = [DDStudent new];
if ([student respondsToSelector:aSelector]) {
return student;
}
return self;
}
这样就会调用到DDStudent
中的run
方法。student
能够处理这条消息,所以这条消息被student
成功处理,消息转发流程提前结束。
- 最后的消息转发
但是如果forwardingTargetForSelector方法中返回的是nil或者self呢?说明没有别的处理者,调用
- (void)forwardInvocation:(NSInvocation *)anInvocation,
在调用forwardInvocation:之前会调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法
来获取这个选择子的方法签名,然后在
-(void)forwardInvocation:(NSInvocation *)anInvocation
方法
中你就可以通过anInvocation拿到相应信息做处理,实例代码如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"forwardInvocation");
DDStudent *student = [DDStudent new];
anInvocation.target = student;
anInvocation.selector = @selector(run);
[anInvocation invoke];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"methodSignatureForSelector");
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
这个消息就会被Student处理掉了。
完整的转发流程如下:
那么最后消息未能处理的时候,还会调用到- (void)doesNotRecognizeSelector:(SEL)aSelector
这个方法,如果这个方法也没有实现,那么就会抛出没有找到该方法实现的错误了。所以我们也可以在这个方法中做些文章,避免掉crash,但是只建议在线上环境的时候做处理,实际开发过程中还要把异常抛出来。
Runtime的使用场景就不多介绍了,因为用的太多了。