美文网首页
第八篇:消息的转发

第八篇:消息的转发

作者: 坚持才会看到希望 | 来源:发表于2022-05-11 15:59 被阅读0次
 for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
            //再一次从cache里面去找imp
            //目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            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)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

在上面我们研究源码时候知道,其objc_send这个查找方法,会现在其本类里面找,如果找不到的话会去父类里找,如果还没找到会进行循环直到找到NSObject为nil的时候,这个NSObject是万类之主。如果还没找到的时候,就会进行赋值imp = forward_imp,我们点击这个forward_imp进去发现是如下代码, const IMP forward_imp = (IMP)_objc_msgForward_impcache,我们全局进行搜索下_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

在所有方法都没找到的时候其会调用我们oc里这段代码,想必大家也经常能够看到。

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

同时在方法找不到的时候在其前面会调用 动态决议方法,这两个方法如下:

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

那我们来验证下,当找不到示例方法的时候会走动态决议方法,同时我们再自己实现这个方法。
首先我们在mian函数里定义了一个HPWPerson对象,调用test1这个方法,但是这个方法我们在HPWPerson类里面并没有去实现,如果不实现的话就会奔溃。所以我们在奔溃前可以进行重新resolveInstanceMethod这个方法,然后在这个方法里把我们test1方法换成method1方法调用,那这样就不会奔溃了。结果如下图控制台输出,也可以正常进行一个打印。在消息进行转换后其方法还是会放到当前类的cache里缓存,存的是method1,因为method1是imp也就是方法的实现。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        HPWPerson *p = [[HPWPerson alloc] init];
        [p test1];
    }
    return 0;
}

HPWPerson类

#import "HPWPerson.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation HPWPerson

+(BOOL)resolveInstanceMethod:(SEL)sel {
//    NSLog(@"%s,%@",__func__,NSStringFromSelector(sel));
        /**
         const char *types
         描述方法参数类型的字符数组。描述方法参数类型的字符数组的第一个字符是代表返回值的类型,后面的字符依次代表参数的类型,因为Objective-C中的函数会包含两个隐式参数,也就是方法调用者和方法名,所以类型编码的第二个字符一定是@,第三个字符一定是:
         */
        class_addMethod(self.class, sel, class_getMethodImplementation(self.class, @selector(method1)), "v@:");
        return YES;;
 
}

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

@end

运行上面后打印结果如下:

SXObjcDebug[37741:414917] -[HPWPerson method1]

上面讲的是实例方法的动态决议,接着我们来说下类方法的动态决议,类方法用到的动态决议 方法是+(BOOL)resolveClassMethod:(SEL)sel,其和我们上面也是一样的,只是其是对于类方法而已.

+(BOOL) resolveClassMethod:(SEL)sel {
//    NSLog(@"%s,%@",__func__,NSStringFromSelector(sel));
/*const char *types
         描述方法参数类型的字符数组。描述方法参数类型的字符数组的第一个字符是代表返回值的类型,后面的字符依次代表参数的类型,因为Objective-C中的函数会包含两个隐式参数,也就是方法调用者和方法名,所以类型编码的第二个字符一定是@,第三个字符一定是:
         */
        class_addMethod(objc_getMetaClass("HPWPerson"), sel, class_getMethodImplementation(objc_getClass("HPWPerson"), @selector(method2)),"v@:");
        return YES;
    }
    //照着苹果文档写
    return NO;
}

+ (void)method2 {
    NSLog(@"%s",__func__);
}

通过上面我们可以把其运用到实际运用中,进行AOP编程。

  ⾯向切⾯编程(AOP)在不修改源代码的情况下,通过运⾏时给程序添加统⼀功能的技术。

埋点就是在应⽤中特定的流程收集⼀些信息,⽤来跟踪应⽤使⽤的状况,然后精准分析⽤户数据。
⽐如⻚⾯停留时间、点击按钮、浏览内容等等。

1.通过上面我们在NSObject添加一个分类,如NSObject+HPW,在这个分类里进行实例方法的动态决议重写,如(BOOL)resolveInstanceMethod:(SEL)sel ,这样就不会因为方法找不到而奔溃了,因为类方法找不到和示例方法找不到都会到NSObject+HPW这个分类里。在这里可以记录哪些方法没有实现。
2.页面停留时间:思路也是在UIVIewcontroller里添加分类UIVIewcontroller+HPW,添加一个<objc/runtime.h>在这个分类里写viewWillapper和viewWillDIsappear来获取停留的时间。然后通过runtime进行方法的交换。就知道点击了哪个界面,和到哪里消失了。

下面这个代码是写在UIVIewcontroller+HPW里的。


WechatIMG1958.jpeg

消息转发

消息转发是发生在动态决议后面的:

1)消息的快速查找
当我们类调用一个方法,这个方法在这个类里没有,可以在该类里使用forwardingTargetForSelector进行消息的快速转发,转发给某个类去实现。这个方法是缓存在HPWTeacher里,因为这个方法已经转发出去了,是HPWTeacher进行调用。这个是实例方法的转发,如果是类方法的话就是+(id)forwardingTargetForSelector:(SEL)aSelector这个方法。

-(id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));

    if (aSelector == @selector(method1)) {
        return [HPWTeacher alloc];
    }
    return nil;
}

如果消息没有进行一个快速转发,就会进行一个慢速转发。我们只要添加了这个代码就不会因为方法找不到而奔溃了。

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法的签名
}


-(void)forwardInvocation:(NSInvocation *)anInvocation {
   
}

消息转发的流程图:


WechatIMG1959.jpeg

相关文章

  • 第八篇:消息的转发

    在上面我们研究源码时候知道,其objc_send这个查找方法,会现在其本类里面找,如果找不到的话会去父类里找,如果...

  • Runtime

    相关简单介绍 消息机制消息传递机制消息转发机制-动态添加方法消息转发机制-快速转发消息转发机制-慢速转发消息转发机...

  • Runtime 消息转发

    目录 消息转发背景知识 消息转发使用方式 消息转发常见问题 消息转发背景知识 1.消息转发的定义Objective...

  • 消息转发机制(动态消息转发)

    例子分析 1)在给程序添加消息转发功能以前,必须覆盖两个方法,即methodSignatureForSelecto...

  • 消息转发

    参考:https://www.jianshu.com/p/76ed71216cde

  • 消息转发

    执行一个没有实现的方法,程序会在运行时挂掉并抛出 unrecognized selector sent to … ...

  • 消息转发

    OC中的方法调用,其实都是转化成objc_msgSend函数调用 1.信息发送 2.动态方法解析 /// 对象消息解析

  • 消息转发

    1. 消息查找 Objective-C 具有很强的动态性,它将静态语言在编译和链接时期做的工作,放置到运行时来处理...

  • 消息转发

    一张图说明消息转发 例如我们进行这样一个操作:People这个类并没有实现perfectGotoSchool这个函...

  • 消息转发

网友评论

      本文标题:第八篇:消息的转发

      本文链接:https://www.haomeiwen.com/subject/pfvhurtx.html