准备工作
讲究runtime之前先将将一个小技巧,通过下面clang命令,可以将实际代码编译成对应的cpp实现文件,到当前目录下,方便研究OC对象本质
clang -rewrite-objc [文件.m】 -o [对应文件名.cpp]
在main中编写了如下方法,其中LSStudent有一个对象方法run,类方法walk
LSStudent *s = [LSStudent new];
[s run];
[LSStudent walk]
而查看用clang编译后的main.cpp会发现,这几行代码,调用方法的直接调用逻辑如下所示
其中出现的objc_msgSend函数,实际上方法的本质就是调用objc_msgSend向指定对象或者指定类发送消息,这里是使用objc_msgSend发送消息,objc_msgSend拥有两个基本参数,第一个为接收消息的对象,第二个为执行的方法
从下面编译的代码可以得知,调用对象方法和类方法分别向对象、类对象发送消息,前面探究isa的时候已经可以得知对象方法在类里面,类方法存放在元类中,可以看到调用objc_msgSend发送指定消息的方法调用来源于他们的isa
runtime中的objc_msgSend的声明方法,在message头文件中,因此发送消息时使用到了该文件
id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
//初始化,前面提到了alloc&&init,可以得知和new的结果是一样的
LSStudent *s = ((LSStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LSStudent"), sel_registerName("new"));
//向对象发送消息
((void (*)(id, SEL))(void *)objc_msgSend)((id)s, sel_registerName("run"));
//向LSStudent类发送消息
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LSStudent"), sel_registerName("walk"));
可以猜出方法的实现imp为下面形式,第一个参数为接收消息对象,第二个为方法
id objImp(id self, SEL _cmd) {
}
发送消息方法常用的两个c函数objc_msgSend、objc_msgSendSuper(平时用的self、super调用方法),分为向某个对象发送消息,向某个父类发送消息,向父类发送消息的时候需要传递objc_super类型的参数,其为一个结构体一共两个参数,一个接受者自己self,另外一个是接收消息对象的类superClass,即用self执行父类selector方法,调用案例代码如下
//父类发送消息的第一个参数objc_super
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
//向某个对象发送消息,可能为实例对象或者类对象
objc_msgSend(s, sel_registerName("run"));
//向父类发送消息
struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);
objc_msgSendSuper(&mySuper, @selector(run));
objc_msgSendSuper与objc_msgSend原理一致,这里只介绍objc_msgSend
objc_msgSend
objc_msgSend是使用汇编编写的api,由于平时我们使用的都是arm64架构的,这里我们就研究他
通过objc源码文件直接搜索到objc_msgSend对应的位置,会发现如下说明,这里就是介绍objc_msgSend的
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
其部分汇编代码实现如下所示
//进入_objc_msgSend实现
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//比较是否为nil
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
//为空的情况,如果支持tagged pointer,直接跳转到后面LNilOrTagged,现在默认走这里
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//不支持是tagged pointer,直接跳转到后面LReturnZero
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class //获取isa
//最终跳转到这个标签执行
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//CacheLookup开始查找缓存
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
//对象为tagged pointer类型为nil的时候这里
LNilOrTagged:
//检查是否为空,执行完毕LReturnZero,后面ret返回这里
b.eq LReturnZero // nil check
GetTaggedClass //获取class
b LGetIsaDone //跳转到LGetIsaDone执行
// SUPPORT_TAGGED_POINTERS
#endif
//通过指令检查是否为为nil
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 //直接结束
通过上面源码可以看到
首先判断是否支持agged pointer类型,目前系统基本上都支持
然后直接跳转到LNilOrTagged标签下执行,检查对象是否为nil,然后获取class
跳转到LGetIsaDone标签,这里面的逻辑就直接列出:
查找缓存,找到直接执行imp
没有找到,去类中查找是否存在,找到了加入到cache中,并执行,找不到准备转发消息,后面就是我们熟悉的c语言了(即走到了非高速查找路线),即从查找imp开始了
于是乎走到了lookUpImpOrForward,不过介绍他之前,看到了一个类,可以先介绍一下_lookUpImpTryCache方法,其源码如下
其逻辑为,可以看到其先看一个类是否发送消息过没如果没有直接调用lookUpImpOrForward查找imp,否则调用缓存,如果缓存中存在则直接命中结束,否则查找
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
接下来到了lookUpImpOrForward方法,其为去class查找方法的流程
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//获取消息转发的imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
runtimeLock.lock();
//检查是否不存在该类
checkIsKnownClass(cls);
//检查该类初始化情况,没有则初始化
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
//从这里开始遍历查找
for (unsigned attempts = unreasonableClassCount();;) {
//如果缓存命中
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 {
//走到这里
// curClass method list.
从当前类中查找指定方法sel
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//找到获取imp
imp = meth->imp(false);缓存在定位到done
goto done;
}
//定位到其父类,如果父类不存在则结束,imp重新给forward_imp方法
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
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.
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)) { //高速缓存存在定位到done
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
//如果上面循环找到父类为空也找不到,最终会路过这里,直接调用resolveMethod_locked方法
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
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
log_and_fill_cache(cls, imp, sel, inst, curClass); //填充cache
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp; 返回最终执行的imp
}
上面介绍到了getMethodNoSuper_nolock,会走到class里面的rw的methods方法列表中查找指定方法(isa篇章有介绍)
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
前面里面交代了在该类中查找是否有指定方法,然后会遍历父类查找高速缓存,然后父类方法列表查找,找到了则跳转done,加入高速缓存,否则返回已经赋值消息转发imp,如果到父类都未找到,则直接调用resolveMethod_locked方法
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]
//不是元类则调用instance的动态解析方法,此时为调用对象方法失败走到这里
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//为元类,调用resolveClassMethod动态解析方法,此时为调用类方法失败走到这里
resolveClassMethod(inst, sel, cls);
//这里又重复了一遍流程,会继续元类向上查找元类等父类是否实现了,如果还找不到则调用元类的resolveInstanceMethod方法(毕竟类对象也可以实现以自己为实例对象的动态解析方法,个人猜测)
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
resolveInstanceMethod与resolveClassMethod相似,均直接发送消息调用该方法来生成指定方法
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//检查是否实现了resolveInstanceMethod方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//调用resolveInstanceMethod方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//查看缓存是否已经有了resolveInstanceMethod添加的方法
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
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));
}
}
}
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//检查是否实现了resolveInstanceMethod方法
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
//因为发送消息会去isa里面找方法, 所以不能向元类发送消息,需要获取到类对象发送
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//查看缓存是否已经有了resolveInstanceMethod添加的方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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
//查看缓存是否已经有了resolveInstanceMethod添加的方法
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
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));
}
}
}
上面动态解析如果还失败了,则会调用前面的消息转发的imp实现_objc_msgForward_impcache,即消息转发,我们可以通过一种更简洁的形式,查看后面到崩溃前走了那些方法
objc/message里面声明了这个一个函数instrumentObjcMessageSends,我们只需要导入函数将其声明,然后调用指定方法即可,这样就可以看到方法的调用流程了,实现案例如下所示
instrumentObjcMessageSends代码执行后,会生成一个日志文件保存到本地
目录:电脑->private->tem->msgSend-数字(这个就是生成的文件)
#import<objc/message.h>
extern void instrumentObjcMessageSends(BOOL);
instrumentObjcMessageSends(YES);
[LSPerson walk];
instrumentObjcMessageSends(NO);
消息转发
通过上面的代码实现,于是乎可以引出消息的转发流程,如下图所示
消息转发过程一共经过了上面几个步骤,这是apple给广大开发者留下的另一套防线,开发者可以自定义crash,或者方崩溃方案处理
动态解析resolveInstanceMethod、resolveClassMethod
上面的发送消息机制介绍了,在最终找不到方法时会走到resolveInstanceMethod、resolveClassMethod中寻找解决方案,当该方法实现,并且调用成功时,会从class里面寻找是否真的解决了,如果没解决,继续向下或者崩溃;如果动态方法添加了方法解决了,那么则不会崩溃,问题解决
动态解析作为防崩溃的第一道防线,下面介绍resolveInstanceMethod、resolveClassMethod是如何解决崩溃
//对象方法的动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//发现了run方法没有实现,则自己手动实现,添加的class的methods当中
if (sel == @selector(run)) {
// 我们动态解析我们的 对象方法
NSLog(@"对象方法解析走这里");
SEL runSEL = @selector(runReplace);
Method runM= class_getInstanceMethod(self, runSEL);
IMP runImp = method_getImplementation(runM);
const char *type = method_getTypeEncoding(runM);
return class_addMethod(self, sel, runImp, type);
}
return [super resolveInstanceMethod:sel];
}
//类方法的动态解析
+ (BOOL)resolveClassMethod:(SEL)sel{
//发现了run类方法没有实现,则自己手动实现,添加的class的methods当中,注意类方法和对象方法分别储存在哪里
if (sel == @selector(walk)) {
NSLog(@"类方法解析走这里");
SEL walkSEL = @selector(walkRepalce);
// 类方法就存在我们的元类的方法列表
//Method walkdM1= class_getClassMethod(self, walkSEL); //获取直接该对象的类方法
Method walkM= class_getInstanceMethod(object_getClass(self), walkSEL); //获取该类的对象方法
IMP walkImp = method_getImplementation(walkM);
const char *type = method_getTypeEncoding(walkM);
NSLog(@"%s",type);
return class_addMethod(object_getClass(self), sel, walkImp, type);
}
return [super resolveClassMethod:sel];
}
于是乎通过动态解析方法,解决了崩溃,那么如果此手段仍然某种原因崩溃了,可以尝试下一步消息转发
重定向forwardingTargetForSelector
此时消息转发将被调用的方法转交给别人解决,当前类没有run方法,那么就转发给有run方法的LSStudent对象或者LSStudent类来解决,处理方案如下所示
//实例方法的重定向,可以重定向给其他对象来执行
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
// 转发给我们的LSStudent 对象
return [LSStudent new];
}
return [super forwardingTargetForSelector:aSelector];
}
//类方法的重定向,可以重定向给其他类来执行
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(walk)) {
// 转发给我们的LSStudent 对象
return [LSStudent class];
}
return [super forwardingTargetForSelector:aSelector];
}
如果LSStudent仍然没有实现run或者walk方法,那么还有最后一道防线,方法签名
消息转发forwardInvocation
消息转发经历了两个方法methodSignatureForSelector和forwardInvocation
以对象方法为例,对新方法签名,转发给其他可以执行的对象执行,类方法同理,这里就举例类方法了
//给方法签名,使用新的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
// forwardingTargetForSelector 没有实现 就只能方法签名了
//下面两个是获取已有的方法和签名type
Method method = class_getInstanceMethod(object_getClass(self), @selector(runReplace));
const char *type = method_getTypeEncoding(method);
//返回值+默认的前两个参数(self,当前sel方法)+自己的参数
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
//查找到那个对象右该方法,直接转发给他执行即可,可以是某个对象的子类,等
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
上面给方法签名提到了一个签名所需要的method_getTypeEncoding,这个是方法的类型简称
v 返回值void
@ 表示对象参数
: SEL类型参数
@ 示对象参数
类型对照表如下所示
总结
通过clang命令可以将当前的OC方法调用,编译成c调用研究
objc_msgSendSuper调用指定的父类方法,objc_msgSend为正常方法调用,又称发送消息
objc_msgSend发送消息的过程,先到高速缓存中查找imp实现,如果找不到,会到class中的rw中的methods方法列表查找,然后加入高速缓存并调用该方法,如果该类找不到,会继续向父类查找,继续高速缓存,rw的methods中查找,最终如果找不到,会开始动态解析、消息转发、重定向相关处理