Runtime之方法的本质

670 阅读12分钟

准备工作

讲究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中查找,最终如果找不到,会开始动态解析、消息转发、重定向相关处理