Runtime运行时
Runtime简单翻译就是运行时,区别于编译阶段,运行时实在运行时进行决议。比如当我们定义一个方法而没有具体实现,当调用方法时,编译阶段并不会报错,但当程序运行起来时就会出现崩溃。这也是为什么说OC是一门动态语言。
Runtime的主要内容就是消息的传递,以及消息的转发流程。下面将依照主要内容依次了解Runtime的相关内容。方法调用的本质是消息,通过接受者和SEL去查找IMP并且执行IMP的过程
Runtime的两个版本: modern 和 legacy。我们现在用的 Objective-C 2.0 采用的是现行 (Modern) 版的 Runtime 系统
利用clang进行初步探索
当我们调用一个方法时,编译器会将代码进行编译。当我们调用[person eat]方法,编译器会生成((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("eat"));
方法调用的过程就是消息发送的过程,对于消息的发送的重要参数receiver(消息的接受者)和SEL(方法)以及args(参数)
快速查找流程
person调用eat方法- 调用objc_msgSend方法在cache中查找(快速查找流程)
- 通过
person的isa找到class并从methods中查找方法(慢速查找流程) - 如果没找到继续向类的父类中查找
- 如果找到就调用
eat方法,如果没有就会进入消息转发流程 - 如果消息转发流程不作处理,就会崩溃
unrecognized selector sent to instance
如果当前方法在父类中找到,会调用objc_msgSendSuper(),参数为objc_super类型
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
其中的属性receiver为person,super_class为person的父类,为第一个要去查找的类。
objc_msgSend是使用汇编语言实现的,稍微看一下吧,我也看不懂。
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // 判断p0是否为空,实际上就是判断receiver是否为空并且判定是否为tagged pointer类型
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa x0对象的首地址
GetClassFromIsa_p16 p13, 1, x0 // p16 = class 经过运算获取class给p16
// 找到class获取缓存中的方法
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
在上述的过程中通过receiver获取到了当前消息接收者的isa以及class,下面进行方法查找CacheLookup,找到这个定义
// CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached,(MissLabelConstant默认参数)
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // // stash the original isa
LLookupStart\Function: // 开始查找function
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// #define CACHE (2 * __SIZEOF_POINTER__) CACHE = 16
// x16平移16就得到了cache, p11 = cache
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES //#define CONFIG_USE_PREOPT_CACHES 1
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
// p11与上#0x0000fffffffffffe得到buckets
and p10, p11, #0x0000fffffffffffe // p10 = buckets
// p11和0比较,如果有就执行 LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
p11如果不为空执行LLookupPreopt\Function
慢速查找流程
如果缓存找不到,开始调用lookUpImpOrForward进入慢速查找流程
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
......
1、查找class是否已经注册到表中
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
2.进行循环查找
for (unsigned attempts = unreasonableClassCount();;) {
3.在缓存中再次查找
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
4.查找方法列表
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
5.找到IMP,执行done
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
7.如果找到nil还没有找到,返回forward_imp,进行下一个流程
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
6.当前类没有找到,到父类中查找,方法是一样的,先快速查找,再慢速查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
// 8.没找到imp接下来要进入消息转发机制
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
5.1.找到方法,将方法缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
checkIsKnownClass(cls);查找注册表- 进行循环查找,查找自己,如果没有向上查找父类,直到nil
- 调用
getMethodNoSuper_nolock进行查找,实际内部进行了二分查找 - 如果还没有找到,
imp = forward_imp进行下一个流程,方法转发
方法转发
当调用未实现的方法,会导致崩溃
[GPerson playbasketball]: unrecognized selector sent to instance 0x101341080'
导致崩溃的原因是没有找到对应的IMP,最后调用unrecognized方法,那么中间经历了哪些呢
上文中未能找到方法实现会给imp赋值forward_imp(_objc_msgForward_impcache)
_objc_msgForward_impcache在汇编中指向__objc_msgForward最终我们找到
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
实际上调用__objc_forward_handler,最后我们找到其方法实现
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
至此我们找到了unrecognized selector sent to instance
为了让我们能够有效地对崩溃进行处理,苹果还是给了我们一些处理的机会,进入方法转发,上文中的方法resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 如果不是类方法,调用对象方法resolveInstanceMethod
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 如果是类方法,调用类方法resolveClassMethod
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 当开发者对在给出的方法中对未找到的IMP做出相应的处理,会再次调用方法查找IMP
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
动态方法决议
当方法没有找到的时候,进入快速转发流程,类方法调用resolveClassMethod,对象方法调用resolveInstanceMethod,此时系统提供给你解决的时机,可以在方法中动态的为sel添加IMP方法实现,将当前playFootball的IMP赋值给playbasketball,此时不会崩溃并调用playFootball方法实现。
- (void)playFootball {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(playbasketball)) {
IMP playFootballImp = class_getMethodImplementation(self, @selector(playFootball));
Method method = class_getInstanceMethod(self, @selector(playFootball));
const char *type = method_getTypeEncoding(method);
return class_addMethod(GPerson.class, sel, playFootballImp, type);
}
return [super resolveInstanceMethod:sel];
}
代码中可以看到,类的方法有所不同,调用resolveClassMethod后如果仍然没有找到,会调用resolveInstanceMethod,所以其实也可以都放到resolveInstanceMethod中进行处理
如果当前resolveInstanceMethod:没有做处理
动态决议阶段如果没做出有效的处理,lookUpImpOrForward就会返回nil,至此源码的探索已经不知道下一步该如何
在工程中引入extern void instrumentObjcMessageSends(BOOL flag);
GPerson *person = [[GPerson alloc] init];
instrumentObjcMessageSends(YES);
[person playbasketball];
instrumentObjcMessageSends(NO);
在调用的时候就会打印出调用日志,路径为/private/tmp/msgSends-xxxx,打开文件查看调用方法
+ GPerson NSObject resolveInstanceMethod:
+ GPerson NSObject resolveInstanceMethod:
- GPerson NSObject forwardingTargetForSelector:
- GPerson NSObject forwardingTargetForSelector:
- GPerson NSObject methodSignatureForSelector:
- GPerson NSObject methodSignatureForSelector:
+ GPerson NSObject resolveInstanceMethod:
+ GPerson NSObject resolveInstanceMethod:
- GPerson NSObject doesNotRecognizeSelector:
- GPerson NSObject doesNotRecognizeSelector:
至此我们看到,在调用为实现的方法之前分别调用了resolveInstanceMethod``forwardingTargetForSelector``methodSignatureForSelector
快速转发流程
在调用完resolveInstanceMethod之后,如果在这里也没有被处理,进入forwardingTargetForSelector方法进行快速转发流程。
方法返回一个id类型,如果当前类无法处理你可以返回一个实现了该方法的对象,让其进行处理
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(playbasketball)) {
return [GPlayer alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
person中未实现的playbasketball方法让player去执行
至此快速消息转发结束
慢速转发流程
在快速转发流程结束后依旧没有得到有效的处理,那么进入慢速转发流程,调用methodSignatureForSelector,通常搭配forwardInvocation一起使用
在methodSignatureForSelector进行方法签名处理,并实现forwardInvocation方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(playbasketball)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(playbasketball)) {
anInvocation.target = [GPlayer alloc];
[anInvocation invoke];
}
}
此时不进行方法的处理已经不在崩溃,对于传过来的invocation可处理可不处理,如果需要处理,可以对anInvocation进行设置,包括target和sel以及参数等信息。