Objective-C基础之六(Runtime之深入理解objc_msgSend)

2,501 阅读20分钟

objc_msgSend源码解析

源码查找路径

在OC中,所有的方法调用底层都会转换成objc_msgSend方法进行调用,那么objc_msgSend底层是如何实现的呢?现在我们就通过objc源码来了解objc_msgSend的调用流程。

objc源码中查找objc_msgSend方法。发现方法的实现在objc-msg-arm64.s、objc-msg-i386.s等objc-msg开头的文件中都存在,因此我们主要查看objc-msg-arm64.s的实现。

在objc-msg-arm64.s中objc_msgSend是使用汇编来实现的,部分汇编代码如下

//进入 _objc_msgSend 函数
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
    //检查objc_msgSend第一个参数是否为nil
	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    //如果支持tagged pointer,如果p0<=0,执行LNilOrTagged函数
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    //判断objc_msgSend第一个参数是否为nil
	b.eq	LReturnZero		// nil check

	// tagged
	......
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	......
	b	LGetIsaDone
#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

首先会对objc_msgSend的第一个参数,也就是当前接收消息的对象进行nil判断,然后根据比较结果,跳转到LNilOrTagged,LNilOrTagged中会调用LGetIsaDone,在LGetIsaDone中则通过CacheLookup来调用方法的实现。

.macro CacheLookup
	// p1 = SEL, p16 = isa(p1存放着SEL,p16存放这当前消息接收者的isa指针)
	ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
#if !__LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	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

3:	// wrap: p12 = first bucket, w11 = mask
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	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

3:	// double wrap
	JumpMiss $0
	
.endmacro

CacheLookup其实相当于OC中的宏,CacheLookup的功能其实就是去当前类的方法缓存中去查找方法,如果在缓存中找到方法,会调用CacheHit,如果没有找到方法,则会调用CheckMiss,继续查看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
.else
.abort oops
.endif
.endmacro

发现在CheckMiss调用了__objc_msgSend_uncached

.endmacro

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached

在__objc_msgSend_uncached中,调用了MethodTableLookup

.macro MethodTableLookup
	......
	// receiver and selector already in x0 and x1
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3
	......

在MethodTableLookup中调用的__class_lookupMethodAndLoadCache3在汇编代码中查找不到对应的实现,因此我们在整个objc源码中查询class_lookupMethodAndLoadCache3的实现,最终找到C语言实现如下

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

objc-runtime-new.mm中的lookUpImpOrForward函数就是我们最终要找到的核心方法。

核心方法解析

lookUpImpOrForward函数源码如下:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    //是否进行了方法解析操作
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 如果cache为true,则会再次到当前类的缓存中查找方法
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    runtimeLock.lock();
    checkIsKnownClass(cls);
    //判断当前类有没有被实现(也就是类中的data()返回的是否是class_rw_t类型)
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    //判断当前类是否初始化,如果没有初始化,会向类发送+initialize消息
    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

 retry:    
    runtimeLock.assertLocked();

    //再次到当前类的缓存列表中查找方法,如果找到,直接跳转到done执行,因为可能会在运行时动态给类增加方法
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    {
        //到当前类的方法列表中查询
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //如果找到方法,则将方法保存到当前类的缓存中去,然后跳转到done执行
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    {
        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.");
            }
            
            // 先到父类中的缓存中查找
            imp = cache_getImp(curClass, sel);
            if (imp) {
                //判断imp是否就是函数指针_objc_msgForward_impcache
                if (imp != (IMP)_objc_msgForward_impcache) {
                    //imp不是_objc_msgForward_impcache,将此方法保存到当前类的缓存中去,跳转到done继续执行
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                //如果查找到的imp就是_objc_msgForward_impcache函数,那么直接跳出循环
                    break;
                }
            }
            //在父类的方法列表中查找,如果找到,则先将方法缓存到当前类的方法缓存中,然后返回此方法的地址
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //如果在当前类和父类中都没有找到此方法,那么,就进入动态方法解析阶段
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        
        //triedResolver表明当前动态方法解析只会执行一次
        triedResolver = YES;
        goto retry;
    }

    //如果当前类和父类都未实现该方法,并且没有实现动态解析,那么就会进入消息转发阶段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
  1. 首先会去当前currentClass的缓存中查找方法,如果找到,直接返回IMP,方法结束。如果没有找到则执行第2步

之前在汇编代码中就已经查找过缓存了,此处再查找一次缓存是为了防止运行过程中动态给currentClass增加方法。

  1. 到currentClass的方法列表中查询此方法,如果找到方法,则将方法存放到currentClass的cache中去,并且返回IMP,方法结束。如果没找到,则执行第3步。
  2. 根据superClass,通过继承结构到currentClass的父类的方法缓存中查找,如果在缓存中找到方法,则执行第4步。如果在superClass的缓存中没有找到方法,则执行第5步
  3. 判断此方法是否是_objc_msgForward_impcache方法,如果不是,则将此方法存放到currentClass的cache中,并且返IMP,方法结束。如果此方法是_objc_msgForward_impcache,直接执行第6步

_objc_msgForward_impcache其实是一个存放在内存中的函数指针,为汇编实现,内部会调用__objc_msgForward函数。

  1. 到superClass的方法列表中查询方法,如果找到方法,则将此方法保存到currentClass的方法缓存中,然后返回方法地址。如果没找到,则执行第6步
  2. 判断当前是否执行执行过方法解析,如果执行过,则进入第7步。如果没有执行过,进入动态方法解析阶段,并且设置triedResolver=YES,然后重复执行第1步。
  3. 进入消息转发阶段,将_objc_msgForward_impcache函数指针缓存到currentClass的cache中,并且返回_objc_msgForward_impcache的imp。

具体流程图如下:

因此我们可以得出结论,objc_msgSend分为3个步骤

  1. 消息发送(方法查找)
  2. 动态方法解析
  3. 消息转发

方法查找补充

在上文中lookUpImpOrForward函数用来进行方法查找,在函数中使用cache_getImp函数去类对象的缓存中查找方法。使用getMethodNoSuper_nolock函数去类对象的方法列表中去查询,getMethodNoSuper_nolock源码如下:

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{   
    //遍历方法列表,得到method_list_t,通过search_method_list函数去method_list_t中查找对应方法
    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;
}

因为类对象中的class_rw_t中方法列表是method_array_t类型的二维数组,内部存放着method_list_t,因此,getMethodNoSuper_nolock函数就是遍历二维数组,然后拿到对应的method_list_t,最后调用search_method_list函数去method_list_t中查找对应的方法,源码如下:

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    //判断当前method_list_t是否排好序
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        //如果排好序,则使用二分查找,提高效率
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 如果没有排序,则遍历查询
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

    return nil;
}

在search_method_list函数中会对method_list_t是否排好序进行判断,如果method_list_t未排序,则通过遍历去查询方法列表。如果已排序,则会调用findMethodInSortedMethodList进行二分查找,提高查找效率,源码如下:

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    //count >>= 1表示count右移一位,然后赋值给count。相当于count = count / 2
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

findMethodInSortedMethodList函数就是典型的二分查找。有兴趣的同学可以自行去学习有关二分查找的更多知识。

objc_msgSend执行流程

在本文中,所有有关objc_msgSend的伪码,可参考Hmmm, What's that Selector?

消息发送

在OC中方法调用底层是通过调用objc_msgSend函数来执行方法。

[anObject doThings:things];

// 编译之后
objc_msgSend(anObject, @selector(doThings:), things);

objc_msgSend的伪码实现如下

id objc_msgSend(id self, SEL _cmd, ...) {
  //通过self拿到对应的类对象
  Class class = object_getClass(self);
  //去类对象中查找对应的方法实现
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

其中class_getMethodImplementation函数就是方法查找的过程,具体的方法查找过程可参考上文,或者参考之前的文章Objective-C基础之五(Runtime之Class结构解析)

动态方法解析

源码解析

如果在当前类和其父类中都没有实现方法,那么会进行动态方法解析,也就是上文中的resolveMethod函数,在此函数中可以动态为类增加方法。

//如果在当前类和父类中都没有找到此方法,那么,就进入动态方法解析阶段
if (resolver  &&  !triedResolver) {
    runtimeLock.unlock();
    resolveMethod(cls, sel, inst);
    runtimeLock.lock();
        
    //triedResolver表明当前动态方法解析只会执行一次
    triedResolver = YES;
    goto retry;
}

每次方法调用都会判断是否进行过动态方法解析,如果没有进行过动态方法解析,则会调用resolveMethod函数如下

static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}

如果当前类不是元类,那么直接调用resolveInstanceMethod函数,在函数内部会查找类中是否实现了resolveInstanceMethod方法,如果实现了,则会调用resolveInstanceMethod方法。resolveInstanceMethod源码如下

static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    //lookUpImpOrNil内部其实就是调用lookUpImpOrForward方法,去当前类及其父类中查找SEL_resolveInstanceMethod
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //通过objc_msgSend调用resolveInstanceMethod方法
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    msg(cls, SEL_resolveInstanceMethod, sel);
}

上文中lookUpImpOrNil其实内部就是调用lookUpImpOrForward函数去查找SEL_resolveInstanceMethod方法,如果没有找到,并且lookUpImpOrForward返回的imp==_objc_msgForward_impcache时,则返回nil。也就是判断当前类以及它的父类是否实现了SEL_resolveInstanceMethod方法,如果未实现,则不执行下一步的objc_msgSend(cls, SEL_resolveInstanceMethod, 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;
}

如果当前类或者它的父类实现了SEL_resolveInstanceMethod方法,则调用objc_msgSend(cls, SEL_resolveInstanceMethod, sel)重新走一遍消息发送流程。目的就是找到SEL_resolveInstanceMethod方法并且进行调用。

lookUpImpOrForward只是返回方法的地址,具体方法的调用由汇编实现。

如果当前类是元类,那么就会调用resolveClassMethod,具体内部实现和类对象的实现一致。不管是resolveInstanceMethod还是resolveClassMethod,我们都可以在这两个方法中动态的为类和元类增加方法。当执行过动态方法解析之后,会执行以下操作

//triedResolver表明当前动态方法解析只会执行一次
triedResolver = YES;
goto retry;

首先会将triedResolver标记设置为YES,然后跳转到retry标记处重新执行一次方法查找流程,如果我们在resolveInstanceMethod或者resolveClassMethod方法中动态为类新增了方法,那么执行retry的时候会再次到类的缓存或者方法列表中查找到对应的方法并且执行。

Demo实现

  • 首先创建XLPersoon类,然后在XLPerson.h中添加以下声明,但是不添加方法的实现。
@interface XLPerson : NSObject

- (void)testInstance;
+ (void)testClass;

@end
  • 此时,我们在main函数中调用不管是testInstance方法还是testClass方法,最终都会报一个很常见的错误
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[XLPerson test]: unrecognized selector sent to class 0x1000022d0'
  • 然后我们在XLPerson.m中添加动态解析的方法,如下
@implementation XLPerson

- (void)personTestInstance{
    NSLog(@"personTestInstance");
}

+ (void)personTestClass{
    NSLog(@"personTestClass");
}
//对象方法消息转发
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(personTestInstance));
        class_addMethod(self,
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//类方法消息转发
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(test)) {
        Method method = class_getClassMethod(self, @selector(personTestClass));
        class_addMethod(object_getClass(self),
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end
  • 再次调用testInstance方法和testClass方法会发现控制台打印出了如下结果,说明消息转发过程中我们动态为类和元类增加的方法都被调用了。
2020-01-13 10:10:09.364022+0800 Test[74806:8439331] personTestInstance
2020-01-13 10:10:12.285118+0800 Test[74806:8439331] personTestClass

流程图

动态方法解析的流程图如下

消息转发

如果我们没有在消息转发的时候动态为类或者元类增加方法,那么,就会来到最后一步,就是消息转发阶段。消息转发过程因为是不开源的,所以我们先从Demo入手来看一下消息转发的流程。

消息转发Demo

  • 继续使用上文中的XLPerson类,类中还是创建了testInstance和testClass两个方法。之后重新创建XLTeacher类,类中同样创建以下方法,并且进行方法实现
@interface XLTeacher : NSObject

- (void)testInstance;
+ (void)testClass;

@end

@implementation XLTeacher

- (void)testInstance{
    NSLog(@"%s", __func__);
}
+ (void)testClass{
    NSLog(@"%s", __func__);
}

@end
  • 消息转发首先会判断类是否实现了forwardingTargetForSelector方法,如果实现了forwardingTargetForSelector方法,会拿到forwardingTargetForSelector方法返回的实例对象或者类对象,然后通过objc_msgSend(返回的对象,sel)执行消息发送。在XLPerson.m中增加以下方法
@implementation XLPerson
//实例对象方法的转发,需要返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [[XLTeacher alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//类对象方法的转发,需要返回一个类对象
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [XLTeacher class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

在main函数中执行XLPerson的方法testInstance和testClass,会发现最后执行了XLTeacher中的同名方法testInstance和testClass

  • 如果类未实现forwardingTargetForSelector方法,那么首先会判断类是否实现了methodSignatureForSelector方法,如果实现了methodSignatureForSelector方法,会拿到methodSignatureForSelector方法返回的方法签名。如果方法签名为nil,则会执行报错。在XLPerson中增加如下方法,返回对应方法的方法签名
@implementation XLPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

@end
  • 如果methodSignatureForSelector返回的方法签名有效,会调用forwardInvocation方法,其实这个方法是将方法调用者、方法名和方法参数封装成了NSInvocation对象。然后使用NSInvocation对象来获取指定调用者的指定方法,并且进行调用。如下
@implementation XLPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[XLTeacher alloc] init]];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[XLTeacher class]];
}

@end
  • 由于methodSignatureForSelector已经返回了正确的方法签名,所以在forwardInvocation方法中就不需要额外设置方法签名了。并且需要保证方法签名和我们需要调用的方法签名保持一致。
  • 并且以上methodSignatureForSelector方法和forwardInvocation方法都有对应的实例方法和类方法。实例方法需要返回一个实例对象,类方法需要返回类对象。
  • 最后在main函数中执行XLPerson的testInstance方法和testClass方法,会发现最终调用了XLTeacher的testInstance方法和testClass方法,至此,整个消息转发的流程就完成了。

通过伪码进一步了解消息转发的内部实现

首先上文中说到,如果以上三个阶段都没有找到方法的话,程序会报错,报错信息如下:

2020-01-13 11:42:58.563144+0800 Test[89986:8560251] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[XLPerson testInstance]: unrecognized selector sent to instance 0x102856690'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff37632f53 __exceptionPreprocess + 250
	1   libobjc.A.dylib                     0x00007fff6d6f8835 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff376bd106 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff375d96cb ___forwarding___ + 1427
	4   CoreFoundation                      0x00007fff375d90a8 _CF_forwarding_prep_0 + 120
	5   Test                                0x0000000100000d9b main + 91
	6   libdyld.dylib                       0x00007fff6ea5b2e5 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

在报错堆栈中,明显可以看出,执行消息转发是通过___forwarding___来完成的,但是我们在源码中是找不到___forwarding___的实现的,因为对于消息转发这部分的源码并不开源,因此我们可以借助上文提到的文章Hmmm, What's that Selector?来研究___forwarding___实现过程。

void __forwarding__(BOOL isStret, void *frameStackPointer, ...) {
  //通过堆栈指针frameStackPointer获取当前接收者
  id receiver = *(id *)frameStackPointer;
  //通过frameStackPointer+4获取到方法选择器
  SEL sel = *(SEL *)(frameStackPointer + 4);
  //获取到receiver对应的类对象
  Class receiverClass = object_getClass(receiver);
    
  //如果receiverClass实现了forwardingTargetForSelector
  if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
    //调用receiver的forwardingTargetForSelector方法,获取到需要转发的target
    id forwardingTarget = [receiver forwardingTargetForSelector:sel];
    if (forwardingTarget) {
      //然后通过调用objc_msgSend函数重新执行消息发送流程,将forwardingTarget作为第一个参数
      return objc_msgSend(forwardingTarget, sel, ...);
    }
  }
  
  //如果对象已经被释放,则打印错误信息
  const char *className = class_getName(object_getClass(receiver));
  const char *zombiePrefix = "_NSZombie_";
  size_t prefixLen = strlen(zombiePrefix);
  if (strncmp(className, zombiePrefix, prefixLen) == 0) {
    CFLog(kCFLogLevelError,
          @"-[%s %s]: message sent to deallocated instance %p",
          className + prefixLen,
          sel_getName(sel),
          receiver);
    <breakpoint-interrupt>
  }
  //如果receiverClass实现了methodSignatureForSelector方法
  if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
    //调用receiver的methodSignatureForSelector获取到对应的方法签名
    NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
    if (methodSignature) {
      //判断methodSignature是否是结构体,如果不是,则打印错误信息
      BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
      if (signatureIsStret != isStret) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
              sel_getName(sel),
              signatureIsStret ? "" : not,
              isStret ? "" : not);
      }
      //如果receiverClass实现了forwardInvocation方法
      if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
        //根据方法签名和堆栈指针获取到对应的invocation
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature
                                                                          frame:frameStackPointer];
        //调用receiver的forwardInvocation方法,将刚刚生成的invocation传递过去
        [receiver forwardInvocation:invocation];

        void *returnValue = NULL;
        //获取到invocation中的返回值
        [invocation getReturnValue:&value];
        return returnValue;
      } else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
              receiver,
              className);
        return 0;
      }
    }
  }

  const char *selName = sel_getName(sel);
  SEL *registeredSel = sel_getUid(selName);
  //如果消息转发都失败的话走以下错误判断
  if (sel != registeredSel) {
    //如果当前的sel和sel_getUid(selName)获取到的sel不一致
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
          sel,
          selName,
          registeredSel);
  } else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
    //如果当前receiverClass实现了doesNotRecognizeSelector方法,则调用此方法
    [receiver doesNotRecognizeSelector:sel];
  } else {
    //未实现doesNotRecognizeSelector的方法的日志
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
          receiver,
          className);
  }

  // 杀死进程
  kill(getpid(), 9);
}

由上述伪代码,可以得到消息转发的整体流程

  1. 判断当前receiverClass是否实现了forwardingTargetForSelector方法,如果实现了,调用forwardingTargetForSelector方法拿到返回的forwardingTarget,然后通过objc_msgSend(forwardingTarget, sel, ...)函数执行消息发送的流程。如果没有实现,则执行第2步。
  2. 判断当前receiverClass是否实现了methodSignatureForSelector方法,如果没有实现,则执行第5步。如果实现了,则执行第3步。
  3. 后判断当前receiverClass是否实现了forwardInvocation,如果未实现,打印错误信息。如果实现了,则执行第4步。
  4. 通过receiverClass的methodSignatureForSelector方法,拿到方法签名,然后根据方法签名和堆栈指针生成对应的invocation对象,然后调用receiverClass的forwardInvocation方法,将生成的invocation作为参数传过去,并且将方法返回值return回去(如果不存在,则返回NULL)。
  5. 如果没有实现对应的消息转发方法,则调用receiverClass的doesNotRecognizeSelector方法打印错误信息,并且退出当前进程。

消息转发流程图如下

objc_msgSend总结

以上就是objc_msgSend的完整流程,大体可以分为3个阶段,消息发送、动态方法解析和消息转发

消息发送

  1. 首先会去当前receiverClass的缓存中查找方法,如果找到,调用方法。如果没有找到则执行第2步
  2. 到receiverClass的方法列表中查询此方法(也就是到class_rw_t中查找),如果找到方法,则先将方法存放到receiverClass的cache中去,然后调用方法。如果没找到,则执行第3步。
  3. 根据superClass,通过继承结构到receiverClass的父类的方法缓存中查找,如果在缓存中找到方法,则执行第4步。如果在superClass的缓存中没有找到方法,则执行第5步
  4. 判断此方法是否是_objc_msgForward_impcache方法,如果不是,则将此方法存放到receiverClass的cache中,并且返IMP,方法结束。如果此方法是_objc_msgForward_impcache,直接执行第6步
  5. 到superClass的方法列表中查询方法,如果找到方法,则将此方法保存到receiverClass的方法缓存中,然后调用该方法。如果没找到,则执行第6步
  6. 判断当前是否执行执行过方法解析,如果执行过,则进入第7步。如果没有执行过,进入动态方法解析阶段,并且设置triedResolver=YES,然后重复执行第1步。
  7. 进入消息转发阶段,将_objc_msgForward_impcache函数指针缓存到currentClass的cache中,并且调用_objc_msgForward_impcache。

动态方法解析

  1. 如果当前未执行过动态方法解析,那么会调用当前类的resolveInstanceMethod或者resolveClassMethod
  2. 在resolveInstanceMethod或者resolveClassMethod中动态为类增加方法,并且插入到方法列表中
  3. 执行完动态方法解析后,会设置是否进行动态方法解析标志为YES,然后重新执行消息发送的整个流程,如果我们动态为类增加了方法,那么会找到此方法并执行。如果未增加方法,则会执行最后的消息转发。

消息转发

  1. 判断当前receiverClass是否实现了forwardingTargetForSelector方法,如果实现了,调用forwardingTargetForSelector方法拿到返回的forwardingTarget,然后通过objc_msgSend(forwardingTarget, sel, ...)函数执行消息发送的流程。如果没有实现,则执行第2步。
  2. 判断当前receiverClass是否实现了methodSignatureForSelector方法,如果没有实现,则执行第5步。如果实现了,则执行第3步。
  3. 判断当前receiverClass是否实现了forwardInvocation,如果未实现,打印错误信息。如果实现了,则执行第4步。
  4. 通过receiverClass的methodSignatureForSelector方法,拿到方法签名,然后根据方法签名和堆栈指针生成对应的invocation对象,然后调用receiverClass的forwardInvocation方法,将生成的invocation作为参数传过去,并且将方法返回值return回去(如果不存在,则返回NULL)。
  5. 如果没有实现对应的消息转发方法,则调用receiverClass的doesNotRecognizeSelector方法打印错误信息,并且退出当前进程。

消息转发流程简图如下:

结束语

以上内容纯属个人理解,如果有什么不对的地方欢迎留言指正。

一起学习,一起进步~~~