IOS objc_msgSend执行流程

objc_msgSend大家应该不陌生吧,oc的方法调用,其实就是转换为objc_msgSend的函数调用。简答的可以理解为发消息,如果 方法调用 之后出现了经典的错误,unrecognized selector sent to instance... 也可以从以下三个阶段进行分析。

objc_msgSend执行流程可以分为三个阶段

  • 消息发送

    XZdog *dog = [[XZdog alloc]init];
    [dog test];
   //通过生成的 .cpp文件可以看到底层代码的实现
  // objc_msgSend(dog, sel_registerName("test")); 

  • 动态方法解析
@interface XZdog : NSObject
- (void)test;

@end
//方法一
#import "XZdog.h"
#import <objc/runtime.h>

@implementation XZdog

- (void)other{
    NSLog(@"---%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
     struct method_t  *meth=(struct method_t*) class_getInstanceMethod(self, @selector(other));
        NSLog(@"%s %s %p",meth->sel,meth->types,meth->imp);
        class_addMethod(self, sel, meth->imp, meth->types);
        //表示有动态添加方法,这是规范,可以参考官方文档
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


@end



//方法二

@implementation XZdog

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
       Method meth=  class_getInstanceMethod(self, @selector(other));

        class_addMethod(self, sel, method_getImplementation(meth), method_getTypeEncoding(meth));
        //表示有动态添加方法,这是规范,可以参考官方文档
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

//方法三

@implementation XZdog

void c_other(id self ,SEL _cmd){
    NSLog(@"c_other:%@",NSStringFromSelector(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
       
        class_addMethod(self, sel, (IMP)c_other,"v16@0:8");
        //表示有动态添加方法,这是规范,可以参考官方文档
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

我们到runtime源码的 objc-runtime-new.mm 类中看下 _class_lookupMethodAndLoadCache3 方法,以下我摘抄了部分源码。

retry:    
    runtimeLock.assertReading();

    // 类的缓存
//缓存中查找方法 
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            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)
        {
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // 父类方法的缓存
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    //父类方法的缓存列表
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                 
                    break;
                }
            }
            
            // Superclass.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //消息发送者是否是第一次解析 
   //假如没有实现动态添加 triedResolver = YES 进入retry
   //当triedResolver = YES 时,下次就进不了这个方法,直接进入  imp = (IMP)_objc_msgForward_impcache (消息转发)

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        triedResolver = YES;
        goto retry;
    }

    // (消息转发)

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

  • 消息转发

main.m 调用XZBrother *b = [[XZBrother alloc]init];[b test]; 

打印结果:RuntimeTest[1033:83983] -[XZCat test] ,代码如下

@interface XZBrother : NSObject
-(void)test;
@end
#import "XZBrother.h"
#import "XZCat.h"

@implementation XZBrother

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector==@selector(test)) {
        return [[XZCat alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end



@interface XZCat : NSObject
- (void)test;
@end

@implementation XZCat

- (void)test{
    NSLog(@"-----");
    NSLog(@"%s",__func__);
}

@end

由源码可知消息转发来了这个方法:imp = (IMP)_objc_msgForward_impcache;

全局搜索,最后在objc-msg-arm64.s 汇编中发现这个方法的实现

全局搜索下_objc_forward_handler 看下这个方法的实现, 你会发现你看的是下面这一块

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;

然后就想 这都是打印信息啊,没错,runtime的消息转发源码是不开源的,我们都知道当本类没有实现方法的时候,消息转发机制会调用forwardingTargetForSelector方法,假如此时该方法返回的是nil或者没有实现,又会发生什么呢?从国外的大神编写的消息转发的伪代码中可以看出,他会用methodSignatureForSelector:(SEL)aSelector方法。

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector==@selector(test)) {
    //返回值为nil 或者 forwardingTargetForSelector不实现的情况
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}
//返回一个方法的签名:返回值类型/参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// 假如这个地方返回的是nil,代表你放弃了这个方法的处理,就会报经典的错误了,unrecognized selector sent to instance...

   // return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
//NSInvocation封装了方法的调用者,方法名,方法参数
//anInvocation.target 方法的调用者
//anInvocation.selector 方法名
//[anInvocation getArgument:NULL atIndex:0] 方法参数
//方法参数atIndex 这里需要注意下还有两个隐藏的参数。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[XZCat alloc]init]];
}

消息转发之后还是找不到方法的实现就会报最经典的错误   unrecognized selector sent to instance...

以上举例都在对象方法,下面咱们来看看类方法。

@interface XZCat : NSObject
+ (void)eat:(int)cut;
@end

#import "XZCat.h"
@implementation XZCat

+(void)eat:(int)cut{
    NSLog(@"%s",__func__);
}
@end

@interface XZBrother : NSObject
+ (void)eat:(int)cut;
@end

#import "XZBrother.h"
#import "XZCat.h"

@implementation XZBrother
//从老外的源码可以看出此方法是消息转发者直接调用,类方法的reveiver就是类本身。所以
//forwardingTargetForSelector 前面要写上 加好+ 
//但是这个方法是没有智能提示的,没关系,可以先调出对象方法,手动吧前面的- 改成+ 就OK了。
//同理如果forwardingTargetForSelector返回的是nil时,methodSignatureForSelector 和forwardInvocation 都要吧他改成类方法
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector==@selector(eat:)) {
    //objc_msgsend([XZBrother class], aSelector)
        return [XZBrother class];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end 

猜你喜欢

转载自blog.csdn.net/qq_33726122/article/details/84285067