美文网首页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>

相关文章

  • 【iOS-RunTime系列三】objc_msgSend()

    objc_msgSend() OC的方法调用,消息机制,给方法调用者发送消息 objc_msgSend 的执行流程...

  • iOS 消息发送机制(前篇)

    前言 在OC中的方法调用,其实都是转换为objc_msgSend()函数的调用 objc_msgSend的执行流程...

  • iOS底层原理 - Runtime-02

    objc_msgSend执行流程 OC中的方法调用,其实都是转换为objc_msgSend函数的调用objc_ms...

  • Runtime(二)

    (三)、objc_msgSend执行流程 OC中的方法调用,其实都是转换为objc_msgSend函数的调用 ob...

  • runtime-消息机制

    OC方法调用,其实都是转换为objc_msgSend函数调用objc_msgSend的执行流程可以分为3大阶段:消...

  • iOS 常见面试题 -- Runtime

    OC中方法的调用 其实都是转化为 objc_msgSend函数的调用 objc_msgSend函数的执行流程可以分...

  • Runtime(二)

    objc_msgSend执行流程 OC中的方法调用,其实都是转换为objc_msgSend函数的调用 在底层会这么...

  • 消息转发机制

    OC中的方法调用,其实都是转换为objc_msgSend函数的调用 objc_msgSend的执行流程可以分为3大...

  • objc_msgSend执行流程

    OC中的方法调用,其实都是转换为objc_msgSend函数的调用 objc_msgSend的执行流程可以分为3大...

  • Runtime 之 OC中的方法调用objc_msgSend

    OC中的方法调用,其实都是转换为objc_msgSend函数的调用 objc_msgSend的执行流程可以分为3大...

网友评论

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

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