Runtime是C,C++汇编一起写成的API,有两个版本Modern和Legacy,OC2.0之后用的是Modern Version版本,可以运行在iOS2.0和macOS 10.5之后的系统中,本文解析的是objc4-723版本的源码。
都说Objective-C是一门动态运行时语言,那什么是运行时?什么是编译时?
运行时:代码跑起来之后会装载到内存中,提供运行时功能
编译时:正在编译的时间,就是把源代码(高级语言)翻译成机器能识别的语言->机器语言->二进制
Runtime可以做的事情有很多,如
- 黑魔法:动态交换方法、KVO实现等
- 关联对象:给分类添加属性等
- 消息转发:项目中一些防崩溃处理等
它可以做的事情有很多,在这里就探索Runtime的底层是如何实现的,也便深入理解Objective-C这门语言。
Runtime的消息传递过程
在OC调用方法时,我们clang一下,看看对应的C++实现是什么
clang -rewrite-objc main.m -o main.cpp
Person *p = [Person new];
[p sayHello];
//C++实现
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
可以看出,在调用方法时,编译器将它转成了objc_msgSend
消息发送了,在Runtime的执行过程如下
- 1、Runtime先通过对象
p
找到isa
指针,判断isa指针是否为nil
,为nil
直接return。 - 2、若不为空则通过
isa
指针找到当前实例的类对象,在类对象下查找缓存是否有sayHello
方法。 - 3、若找到
sayHello
方法,则直接调用IMP
方法。 - 4、若没找到,则查找当前类对象的方法列表
methodlist
,若找到方法则将其添加到缓存中 - 5、若没找到,则继续到当前类的父类中以相同的方式查找,直到追溯到最上层
NSObject
。 - 6、若还是没有找到,则启用动态方法解析、备用接收者、消息转发三部曲,给程序最后一个机会
- 7、若还是没找到,则Runtime会抛出异常
doesNotRecognizeSelector
。
以上就是Runtime的消息传递过程,我们从上往下慢慢分析。
isa指针
首先是isa指针,什么是isa指针?
打开Runtime源码,全局搜索isa_t
,锁定objc-private.h
类,里面有这么一段代码,isa指针是isa_t的实例。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
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; \
uintptr_t extra_rc : 19
};
#endif
};
OC对象的本质,每个OC对象都含有一个isa指针,__arm64__
之前,isa
仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__
架构之后,apple对isa
进行了优化,变成了一个联合体union
结构,同时使用位域来存储更多的信息。
它是通过isa
的bits
进行位运算,取出响应位置的值,runtime中的isa
是被联合体位域优化过的,它不单单是指向类对象了,而是把64位中的每一位都运用了起来,其中的shiftcls
为33位,代表了类对象的地址,其他的位都有各自的用处。
-
nonpointer
:表示是否对isa指针开启指针优化
0:不开启,表示纯isa指针。 1开启,不单单是类对象的地址,isa中包含了类信息和对象的引用计数等。 -
has_assoc
:关联对象标识位,0没有,1有,没有关联对象会释放的更快 -
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 -
shiftcls
:存储类指针class
的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤用来存储类指针。 -
magic
:固定值为0xd2,用于在调试时分辨对象是否完成初始化 -
weakly_referenced
:表示对象是否被指向或者曾经指向一个 ARC 的弱引用变量,
没有弱引⽤用的对象可以更更快释放。 deallocating:标志对象是否正在释放内存 -
has_sidetable_rc
:当对象的引用计数大于10,以至于无法存储在isa指针中时,用散列表去计数 -
extra_rc
:表示该对象的引用计数,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数⼤于 10, 则需要使⽤到has_sidetable_rc
isa 指针走位图
objc_msgSend的方法调用本质
好了现在清楚了isa指针是怎么一回事儿了,接下来探索方法调用的本质是什么。
打开Runtime源码,全局搜索objc_msgSend(
,找到objc-msg-arm64.s
这个类
objc_msgSend的方法调用分为两部分,汇编和C语言。
1、汇编部分
1、ENTRY _objc_msgSend
2、进入LNilOrTagged
,当数据类型是NSTaggedPointer
或消息名为空时,直接END_ENTRY _objc_msgSend
,不为空则继续执行下一步,NSTaggedPointer
类型的对象采用和isa
一样的联合体位域的方式,可直接从地址中读取出想要的值,一般当数据类型的"value"足够小时,系统会自动转换为NSTaggedPointer
类型,比如NSString
转换为NSTaggedPointerString
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
3、LGetIsaDone
处理完isa
,这一步就是isa指针已经处理完,要么从缓存中找到IMP
,要么执行objc_msgSend_uncached
从方法列表中获取IMP
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
4、CacheLookup NORMAL
去缓存中找IMP
,会有以下2种情况
1)CacheHit
在缓存中找到了则直接calls imp
2)CheckMiss
没找到则执行objc_msgSend_uncached
CacheLookup执行如下,主要看关键字就行。
//代码剔除了混淆理解的部分
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
.endmacro
CacheHit执行如下
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
.endmacro
CheckMiss执行如下
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.endmacro
5、objc_msgSend_uncached
,缓存中没找到则去MethodTableLookup
到方法列表中寻找
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
6、MethodTableLookup
中会到objc-runtime-new.mm
中调用_class_lookupMethodAndLoadCache3
方法,从这里开始便从汇编到C语言执行了
.macro MethodTableLookup
bl __class_lookupMethodAndLoadCache3
.endmacro
2、C语言部分
1、执行_class_lookupMethodAndLoadCache3方法,该方法调用了lookUpImpOrForward函数
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
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.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
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.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// 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;
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.unlockRead();
return imp;
}
2、lookUpImpOrForward 查找imp,主要分为以下几步
- 1)
getMethodNoSuper_nolock(cls, sel);
到method_list
中找,如果找到了method
,则把它存入缓存;如果没找到,则向上遍历父类,步骤也是执行1)、2)步,直到查到根部(root)NSObject。
static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) {
//循环遍历method列表,查询方法
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
- 2)log_and_fill_cache存入缓存
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
//获取类的缓存
cache_t *cache = getCache(cls);
//这里可以知道cache是以方法名作为key
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
//在当前缓存大小大于缓存吃容量的3/4时,扩大缓存,在低于3/4时可以正常存储
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {//满了就扩大缓存容量
cache->expand();
}
把方法的IMP存储到cache中
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
如果上面的流程走完没有找到IMP,那么还有接下来三种补救方法,动态方法解析、备用接收者、消息转发
3、动态方法解析、备用对象、消息转发
Part1、动态方法解析
当前 2 步汇编里的 CacheHit 和 C 语言里的MethodTableLookUp 都没找到 imp 时,会执行_class_resolveMethod
方法,进行第一次补救,意在检查是否动态添加了方法
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //如果cls不是一个元类,说明调用的是一个实例方法
//此时 cls表示的是类,而inst代表的是类的实例
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
//此时 cls表示的是元类,而inst代表的是类
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
- 如果 cls 不是元类,说明此次是类对象调用实例方法,此时 cls 表示的是类对象,inst 表示类对象的实例,sel 表示类的实例方法,调用
_class_resolveInstanceMethod
,如果本例中的 Person 类实现了resolveInstanceMethod
,会再次调用 objc_msgSend - 如果是元类,结合 isa 指针走位图,可知 cls 是元类对象,sel 就是元类的实例方法即类方法,inst 就是类对象,如图所示
实例方法
当类的实例对象调用实例方法时,从上图 isa 走位图可知,cls->ISA()
指向的 Person 类,并不是元类,因此调用的是_class_resolveInstanceMethod
方法,方法解析如下
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//cls->ISA() 此时指向的是元类,判断元类是否实现了resolveInstanceMethod方法
//(相当于判断该类是否有实现 resolveInstanceMethod 的类方法)
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
//如果有实现+ (BOOL)resolveInstanceMethod:(SEL)sel,就重启消息发送流程
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
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
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));
}
}
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
这段代码结合lookUpImpOrNil
函数的几个参数可以解析如下
- 第 1 个参数:
cls->ISA()
,根据上面的 isa 走位图可知,这里指向了 Person 的元类 - 第 2 个参数:
SEL_resolveInstanceMethod
,表示resolveInstanceMethod
方法 - 第 3 个参数:
cls
,表示实例是 Person 类对象
这段代码表示 Person 的元类中是否有resolveInstanceMethod
实例方法,因为类方法在元类中是以实例的形式存在的,所以这段代码可以理解为判断 Person 类是否有类方法resolveInstanceMethod
。
这里也可以看出resolveInstanceMethod
形成了递归,流程可以结合下图理解
即循环判断了 Person 类及其父类是否实现了
resolveInstanceMethod
类方法,最终到 NSObject 结束了递归,NSObject 实现了resolveInstanceMethod
这个方法,只是返回了NO
。系统就是通过这样的方式来避免产生死递归。 看源码可以看到,如果找到了resolveInstanceMethod
方法,系统就会重新执行一遍objc_msgSend
的流程,去回调这个方法。
这里有个面试题
如果调用一个 LGPerson 的 run 实例方法,但 LGPerson 类只是声明,却没有实现 run 方法,而实现了resolveInstanceMethod
方法,执行结果为输出了 2 次“来了 老弟”,让后崩溃,为什么?
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[LGPerson alloc] run];
}
return 0;
}
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"来了 老弟");
return [super resolveInstanceMethod:sel];
}
@end
为什么会执行 2 次,我们在这里断点发现第一次调用堆栈如图所示
objc_msgSend_uncached
之后执行了 1 次resolveInstanceMethod
,这个堆栈可以解释为,objc_msgSend_uncached
缓存查找没有命中后,执行了MethodTableLookUp
,如下面汇编代码所示
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
MethodLookUp
之后没有找到,才执行的resolveInstanceMethod
方法,再看第 2 次堆栈如下
在消息转发流程中,当
forwardingTargetForSelector
返回nil
时就会调methodSignatureForSelector
方法,而他的内部触发了class_getInstanceMethod
方法,如下图所示可以发现这里重新执行了方法查找的过程,导致内部执行了第二次
resolveInstanceMethod
方法。
如果想动态添加方法,可以在这一步通过实现resolveInstanceMethod
和resolveClassMethod
方法来实现,代码如下
//调用对象的实例方法时,先执行resolveInstanceMethod方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
if (sel == @selector(run)) {
SEL readBookSel = @selector(readBook);
Method readM = class_getInstanceMethod(self, readBookSel);
IMP readImp = class_getMethodImplementation(self, readBookSel);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
}
return [super resolveInstanceMethod:sel];
}
//调用类方法时,先执行resolveClassMethod方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(walk)) {
SEL readBookSel = @selector(readBook);
Method readM = class_getInstanceMethod(self, readBookSel);
IMP readImp = class_getMethodImplementation(self, readBookSel);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(object_getClass(self), sel, readImp, type);
}
return [super resolveClassMethod:sel];
}
class_resolveClassMethod 类方法解析
当执行的是类方法调用时([Person walk]这种方式),开始从 Person 类对象的元类里查找resolveClassMethod
方法,一直递归去查找,这里如果找到了,依然会重启消息发送方法(NSObject 同样实现了resolveClassMethod
这个方法,返回了NO
)
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
//此时的cls是元类,inst是类
assert(cls->isMetaClass());
//开始遍历元类是否有resolveClassMethod方法
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;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
跟实例方法不同的是:类方法重启objc_msgSend时传入的参数是_class_getNonMetaClass(cls, inst),而实例方法重启时传的参数是cls(类对象),下面我们来分析一下_class_getNonMetaClass的实现
Class _class_getNonMetaClass(Class cls, id obj)
{
mutex_locker_t lock(runtimeLock);
cls = getNonMetaClass(cls, obj);
assert(cls->isRealized());
return cls;
}
static Class getNonMetaClass(Class metacls, id inst)
{
static int total, named, secondary, sharedcache;
runtimeLock.assertLocked();
realizeClass(metacls);
total++;
//metacls是元类 inst是类
// 如果metacls不是元类了,说明此时传进来的inst类是NSObject,就直接返回元类
if (!metacls->isMetaClass()) return metacls;
// metacls really is a metaclass
// special case for root metaclass
// where inst == inst->ISA() == metacls is possible
//如果元类的isa指向了自己,说明此时元类是根元类 ,返回它的父类 即NSObject
if (metacls->ISA() == metacls) {
Class cls = metacls->superclass;
assert(cls->isRealized());
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
if (cls->ISA() == metacls) return cls;
}
//一般情况
if (inst) {
Class cls = (Class)inst;
realizeClass(cls);
// cls may be a subclass - find the real class for metacls
while (cls && cls->ISA() != metacls) {
//这里主要判断isa是否被篡改 — 异常判断
cls = cls->superclass;
realizeClass(cls);
}
if (cls) {
assert(!cls->isMetaClass());
assert(cls->ISA() == metacls);
//直接返回cls 类对象
return cls;
}
#if DEBUG
_objc_fatal("cls is not an instance of metacls");
#else
// release build: be forgiving and fall through to slow lookups
#endif
}
// 以下为 inst 为空等等异常情况,先不考虑,知道这里返回了类对象即可
// try name lookup
{
Class cls = getClass(metacls->mangledName());
if (cls->ISA() == metacls) {
named++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful by-name metaclass lookups",
named, total, named*100.0/total);
}
realizeClass(cls);
return cls;
}
}
// try secondary table
{
Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
if (cls) {
secondary++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful secondary metaclass lookups",
secondary, total, secondary*100.0/total);
}
assert(cls->ISA() == metacls);
realizeClass(cls);
return cls;
}
}
// try any duplicates in the dyld shared cache
{
Class cls = nil;
int count;
Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count);
if (classes) {
for (int i = 0; i < count; i++) {
if (classes[i]->ISA() == metacls) {
cls = classes[i];
break;
}
}
free(classes);
}
if (cls) {
sharedcache++;
if (PrintInitializing) {
_objc_inform("INITIALIZE: %d/%d (%g%%) "
"successful shared cache metaclass lookups",
sharedcache, total, sharedcache*100.0/total);
}
realizeClass(cls);
return cls;
}
}
_objc_fatal("no class for metaclass %p", (void*)metacls);
}
可以看到,_class_getNonMetaClass
的正常流程还是返回了类对象,为什么要返回类对象呢,我们正常的思路是谁出问题,在谁那里修复(应该在元类里取修复),但是类方法是在元类中的,元类是一个虚拟的类,没办法进行书写,所以苹果就设计了这么一个方式,直接返回类,在类里面去实现resolveClassMethod
。
但是我们看类方法的_class_resolveMethod
里发现,_class_resolveClassMethod
执行完了如果没有找到的话,还有一步操作_class_resolveInstanceMethod
(与上面实例方法步骤一致),该方法会继续从元类里寻找resolveInstanceMethod
方法,直到 NSObject,这也表明了我们可以在 NSObject 扩展里实现resolveInstanceMethod
方法,就可以拦截所有的未实现方法。
Part2、备用接收者
如果动态方法解析依然没有结果,就会进行寻找备用接收者,(id)forwardingTargetForSelector:(SEL)aSelector;
这个forwarding方法在runtime底层是通过汇编调用的,苹果并没有给出源码实现,但是我们可以通过打印 runtime 的方法调用信息(log),来查看 runtime 接下来调用了哪些方法,方式如下:
// 外部引用定义
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//使用区域,将想要查找的方法用instrumentObjcMessageSends圈起来
//开始监听传递参数为YES,结束监听传递参数为NO
instrumentObjcMessageSends(YES);
[LGPerson walk];
instrumentObjcMessageSends(NO);
}
return 0;
}
这样在finder中找到/private/tmp/文件夹,如下图所示位置,找到最新的msgSends-xxxx文件,这个文件就是LGPerson调用walk方法的时候所执行的方法信息
调用信息如图所示
显而易见的,LGPerson在动态方法解析后,继续调用了NSObject的动态方法解析,在都没有imp的情况下调用了forwardingTargetForSeletor(备用接收者)方法,当备用接受者也没有实现时,会调用最后一步methodSignatureForSelector(消息转发)
举例如下:
//寻找备用接收者,让别的类来提供同名的方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk)) {
return [LGStudent new];
}
return [super forwardingTargetForSelector:aSelector];
}
我们可以利用
forwardingTargetForSelector
方法完成哪些动作?
1.找备用接收者处理当前类没有实现的方法(注意:备用接收者的方法只能是实例方法)
2.crash收集
3.防止崩溃
Part3、消息转发
如果备用接收者没有找到IMP方法,则执行消息转发,主要涉及以下两个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
在动态方法解析和备用接收者都无法找到IMP时,系统会调用methodSignatureForSelector
方法,如果方法返回nil,则系统直接调用doesNotRecognizeSelector
方法使程序崩溃并打印崩溃信息;如果返回一个NSMethodSignature
类型的函数签名,则系统会创建一个NSInvocation
对象并调用forwardInvocation
方法,我们可以在方法中指定别的方法或其他对象去执行这个的方法。
例子如下
//若返回函数签名,则继续调用forwardInvocation方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(run)) {
//指定当前类的一个方法作为IMP
// anInvocation.selector = @selector(readBook);
// [anInvocation invoke];
//指定其他类来执行这个IMP
LGStudent *student = [LGStudent new];
[anInvocation invokeWithTarget:student];
}
}
以上就是Runtime消息传递的全部过程。
Runtime博大精深,未完待续~