消息转发流程(上)

Posted WeaterMr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消息转发流程(上)相关的知识,希望对你有一定的参考价值。

消息转发流程(上)

补充

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

系统会制动把我们所有的initialize方法自动调用,还有load方法,c++的构造函数。

分析一个崩溃场景

Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '+[Good number]: unrecognized selector sent to class 0x100008310'

当我们在调用某些方法时当方法没有实现时就会报这个错误。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
     }
}

当所有的方法查找不到时会给imp设置一个默认值 forward_imp

_objc_msgForward_impcache

	STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
#if !__OBJC2__

// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

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

原来,当我们的方法找不到时,系统会返回一个默认的imp,即当前的报错信息。当遇到报错,难道只能看着程序崩溃?是否还有补救的机会呢?看下面流程。

对象方法动态决议

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
    return imp;
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(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        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
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    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
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
  • BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel); 系统自动发送消息
  • IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); 在表里继续查找
  • 即实现resolveInstanceMethod方法将不会报错,容错处理。
        Good *good = [Good alloc];
        [good name];
- (void)number{
    NSLog(@"我执行了number方法");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(name)) {
        IMP numberImp     = class_getMethodImplementation(self, @selector(number));
        Method method    = class_getInstanceMethod(self, @selector(number));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, numberImp, type);
    }
    
    NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));

    return [super resolveInstanceMethod:sel];
}

2021-07-05 23:14:11.470286+0800[2309:90947] 我执行了number方法

如果我们没有实现resolveInstanceMethod: 方法系统默认会实现。

类方法动态决议

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    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);
        }
    }
    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
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

}

流程和对象方法很相似,也是实现对应的resolveClassMethod

        Good *good = [Good alloc];
        [Good name];
// 元类以对象方法的方法
+ (BOOL)resolveClassMethod:(SEL)sel{
   
    NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));
    
    if (sel == @selector(name)) {
        IMP age     = class_getMethodImplementation(objc_getMetaClass("Good"), @selector(age));
        Method method    = class_getInstanceMethod(objc_getMetaClass("Good"), @selector(age));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("Good"), sel, sayNBImp, type);
    }

    return [super resolveClassMethod:sel];
}

也可以对NSObjece添加一个扩展实现对应的resolveInstanceMethod原理主要是根据isa的走位图来解释为什么只需要实现resolveInstanceMethod

#import "NSObject+me.h"

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业
    // 处理 sel -> imp
    
    NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));

    if (sel == @selector(number)) {
        IMP ageImp     = class_getMethodImplementation(self, @selector(age));
        Method method    = class_getInstanceMethod(self, @selector(age));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, ageImp, type);
    }else  if (sel == @selector(bad)) {
        IMP badImp     = class_getMethodImplementation(objc_getMetaClass("Good"), @selector(name));
        Method method    = class_getInstanceMethod(objc_getMetaClass("Good"), @selector(name));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("Good"), sel, badImp, type);
    }
    return NO;
}

通过NSObject添加分类,可以处理所有的方法找不到的问题,也可以对方法进行分类,然后根据方法名字进行错误分析归类。日志上次,hook等操作。

什么是面向切面编程AOP

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程
面向切面编程(AOPAspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承多态封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
如果将段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。这样会产生耦合了,抽出的类改变会影响使用的类。AOP 这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOPOOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

以上是关于消息转发流程(上)的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之消息的快速与慢速转发 - 11

objc_msgSend消息传递学习笔记 – 消息转发

消息转发流程

消息转发流程

iOS底层开发消息发送与转发流程

.Net Core&RabbitMQ消息转发可靠机制(上)