美文网首页OC底层
OC方法调用 objc_msgSend 流程(下)

OC方法调用 objc_msgSend 流程(下)

作者: H丶ym | 来源:发表于2020-10-09 15:39 被阅读0次

    OC底层原理学习

    本章主要探索 动态方法决议和消息转发

    在方法没有找到的情况下,其实并不是直接就崩溃,苹果大大给我了我们3次复活的机会

    1. 动态方法决议
    2. 快速转发
    3. 慢速转发
    image.png

    动态方法决议

    触发时机:lookUpImpOrForward慢速查找没有找到时

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
    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 (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
                resolveInstanceMethod(inst, sel, cls);
            }
        }
    
        // chances are that calling the resolver have populated the cache
        // so attempt using it
        //如果方法解析中将其实现指向其他方法,则继续走方法查找流程
        return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
    }
    

    对象方法的动态决议方法 resolveInstanceMethod
    类方法动态决议方法 resolveClassMethod

    验证一下,将之前实现的方法注释掉

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person * person = [Person alloc];
            [person objcMethod]; //将实现注释掉
            [Person classMethod]; //将实现注释掉
        }
        return 0;
    }
    
    #import "Person.h"
    #import "objc-runtime.h"
    @interface Person()
    {
        NSString * _cybl; //成员变量
    }
    
    @property(nonatomic,copy)NSString * name;
    
    @end
    
    @implementation Person
    
    ////类方法
    //+(void)classMethod{
    //
    //}
    //
    //对象方法
    //-(void)objcMethod{
    //
    //}
    
    - (void)sayMaster {
        NSLog(@"sayMaster");
    }
    
    + (void)sayClassMaster {
        NSLog(@"sayClassMaster");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(objcMethod)){
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
            //获取sayMaster方法的imp
            IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
            //获取sayMaster的实例方法
            Method sayMethod  = class_getInstanceMethod(self, @selector(sayMaster));
            //获取sayMaster的丰富签名
            const char *type = method_getTypeEncoding(sayMethod);
            //将sel的实现指向sayMaster
            return class_addMethod(self, sel, imp, type);
        }
        return [super resolveInstanceMethod:sel];
    }
    
    + (BOOL)resolveClassMethod:(SEL)sel {
        if (sel == @selector(classMethod)) {
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
            
            IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(sayClassMaster));
            Method classMethod  = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(sayClassMaster));
            const char *type = method_getTypeEncoding(classMethod);
            return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
        }
        
        return [super resolveClassMethod:sel];
    }
    
    @end
    

    运行结果

    根据isa走势图我们可以得到最后都会来到NSObject的对象方法中查找,我们可以给NSObject写个分类,然后统一处理,防止崩溃。但是万一系统

    #import "NSObject+ResolveCategory.h"
    #import "objc-runtime.h"
    
    @implementation NSObject (ResolveCategory)
    
    - (void)sayMaster {
        NSLog(@"sayMaster");
    }
    
    + (void)sayClassMaster {
        NSLog(@"sayClassMaster");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(objcMethod)){
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
            //获取sayMaster方法的imp
            IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
            //获取sayMaster的实例方法
            Method sayMethod  = class_getInstanceMethod(self, @selector(sayMaster));
            //获取sayMaster的丰富签名
            const char *type = method_getTypeEncoding(sayMethod);
            //将sel的实现指向sayMaster
            return class_addMethod(self, sel, imp, type);
        }
        if (sel == @selector(classMethod)) {
            NSLog(@"%@ 来了", NSStringFromSelector(sel));
    
            IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(sayClassMaster));
            Method classMethod  = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(sayClassMaster));
            const char *type = method_getTypeEncoding(classMethod);
            return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
        }else{
            NSLog(@"系统的方法 %@ 来了", NSStringFromSelector(sel));
        }
        return false;
    }
    
    @end
    

    输入日志

    2020-12-14 14:11:22.366190+0800 Example[44356:242373] 系统的方法 _dynamicContextEvaluation:patternString: 来了
    2020-12-14 14:11:22.366835+0800 Example[44356:242373] 系统的方法 descriptionWithLocale: 来了
    2020-12-14 14:11:22.366994+0800 Example[44356:242373] 系统的方法 _dynamicContextEvaluation:patternString: 来了
    2020-12-14 14:11:22.367117+0800 Example[44356:242373] 系统的方法 _dynamicContextEvaluation:patternString: 来了
    2020-12-14 14:11:22.367208+0800 Example[44356:242373] objcMethod 来了
    2020-12-14 14:11:22.367289+0800 Example[44356:242373] sayMaster
    2020-12-14 14:11:22.367403+0800 Example[44356:242373] 系统的方法 _dynamicContextEvaluation:patternString: 来了
    2020-12-14 14:11:22.367506+0800 Example[44356:242373] 系统的方法 descriptionWithLocale: 来了
    2020-12-14 14:11:22.367596+0800 Example[44356:242373] classMethod 来了
    2020-12-14 14:11:22.367674+0800 Example[44356:242373] sayClassMaster
    

    之前想过,只要走到动态方法决议里,就认为是要崩溃了,直接替换,发现不行,因为底层源码中,有调用resolveInstanceMethod的时候,也就是说你必须知道sel的名字
    解决方案:我们本质是想替换非系统方法,也就是我们自己写的方法,那问题的根本就需要区分我们自己的方法和系统的方法,如果区分呢,把自己写的所有方法加个前缀比如ym_sayHello,用前缀来判断,哈哈哈。。。
    个人觉得在这一层我其实也干不了啥,再学习学习,待补充吧

    快速转发

    forwardingTargetForSelector,将未实现的方法转发给NSObject,由NSObject去实现
    NSObject的分类中添加如下代码

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
        return [NSObject new];
    }
    
    //类方法
    +(void)classMethod{
        NSLog(@"NSObject -- classMethod");
    }
    
    //对象方法
    -(void)objcMethod{
        NSLog(@"NSObject -- objcMethod");
    }
    

    运行日志

    2020-12-14 14:38:05.804201+0800 Example[46180:258610] NSObject -- objcMethod
    2020-12-14 14:38:05.804822+0800 Example[46180:258610] NSObject -- classMethod
    Program ended with exit code: 0
    

    慢速转发

    快速转发没找到时,来到最后一次机会
    methodSignatureForSelector
    forwardInvocation
    在Person中实现方法

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%s - %@",__func__,anInvocation);
    }
    

    输入日志

    2020-12-14 14:45:24.405470+0800 Example[46687:263923] -[Person methodSignatureForSelector:] - objcMethod
    2020-12-14 14:45:24.406279+0800 Example[46687:263923] -[Person forwardInvocation:] - <NSInvocation: 0x101134550>
    

    相关文章

      网友评论

        本文标题:OC方法调用 objc_msgSend 流程(下)

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