Runtime之消息与方法

573 阅读8分钟

Runtime运行时

Runtime简单翻译就是运行时,区别于编译阶段,运行时实在运行时进行决议。比如当我们定义一个方法而没有具体实现,当调用方法时,编译阶段并不会报错,但当程序运行起来时就会出现崩溃。这也是为什么说OC是一门动态语言。

Runtime的主要内容就是消息的传递,以及消息的转发流程。下面将依照主要内容依次了解Runtime的相关内容。方法调用的本质是消息,通过接受者和SEL去查找IMP并且执行IMP的过程

Runtime的两个版本: modernlegacy。我们现在用的 Objective-C 2.0 采用的是现行 (Modern) 版的 Runtime 系统

利用clang进行初步探索

当我们调用一个方法时,编译器会将代码进行编译。当我们调用[person eat]方法,编译器会生成((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("eat"));

image.png

方法调用的过程就是消息发送的过程,对于消息的发送的重要参数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 */
};

其中的属性receiverpersonsuper_classperson的父类,为第一个要去查找的类。

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);

image.png 在调用的时候就会打印出调用日志,路径为/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去执行

image.png 至此快速消息转发结束

慢速转发流程

在快速转发流程结束后依旧没有得到有效的处理,那么进入慢速转发流程,调用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以及参数等信息。