美文网首页
iOS-浅谈OC中的消息机制

iOS-浅谈OC中的消息机制

作者: 晴天ccc | 来源:发表于2019-06-06 09:56 被阅读0次

    目录

    • 简介
    • 消息发送objc_msgSend
    • 动态方法解析
    • 消息转发
      ---- 转发接收者
      ---- 转发调用
    • 补充
      ---- 方法签名
      ---- 类方法消息转发
      ---- invocation
      ---- @dynamic

    简介

    OC方法调用,其实都是发送消息,底层使用objc_msgSend进行发送。主要分为三个阶段:

    • 第一阶段:消息发送
    • 第二阶段:动态方法解析
    • 第三阶段:消息转发

    消息发送objc_msgSend

    调用方法底层是通过objc_msgSend实现的。
    objc_msgSend方法中有两个参数,一个是消息接收者(receiver),一个是消息名称

    [person run];
    

    底层实现

    objc_msgSend(person, sel_registerName("run"))
    

    为了方便阅读可以写成:

    objc_msgSend(person, @selector(run))
    

    objc_msgSend源码是由汇编和C++代码实现的,消息发送过程如下:

    动态方法解析

    • 当消息发送阶段结束,没有找到方法,就会进入动态方法解析阶段。
    • 系统给实例方法提供了resolveInstanceMethod方法,给类方法提供了resolveClassMethod方法,给没找到的方法添加实现的机会。
    • 我们可以实现resolveInstanceMethodresolveClassMethod,来动态添加方法实现。

    比如person实例对象调用了run方法,但是run方法没有实现,可以按照下面方式给run方法添加实现。

    - (void)other {
        NSLog(@"%s",__func__);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSLog(@"%s",__func__);
    
        // 动态添加run方法实现
        if (sel == @selector(run)) {
            // 获取其它方法
            Method method = class_getInstanceMethod(self, @selector(other));
            // 给sel(run)添加方法实现
            class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
            return YES;
        }
        return  [super resolveInstanceMethod:sel];
    }
    

    也可以添加c方法实现:

    void c_other(id self, SEL _cmd)
    {
        NSLog(@"c_other-%@-%@",self,NSStringFromSelector(_cmd));
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        
        NSLog(@"%s",__func__);
        
        // 动态添加run方法实现
        if (sel == @selector(run)) {
            class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
            return YES;
        }
        
        return  [super resolveInstanceMethod:sel];
    }
    

    类方法原理相同,注意添加方法的对象应传入类对象:

    + (BOOL)resolveClassMethod:(SEL)sel {
        // 动态添加run方法实现
        if (sel == @selector(run)) {
            class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
            return YES;
        }
        return  [super resolveInstanceMethod:sel];
    }
    

    动态解析过程如下:

    动态解析过后,会重新走“消息发送”的流程。从receiverClass的cache中查找方法这一步开始执行。

    消息转发

    • 转发接收者

    动态方法解析失败后会开始快速转发流程,指定一个能处理该方法的接收者,让接收者执行此方法。比如将Person的run方法,转发给Cat执行run方法:

    // 转发接收者,指定一个新的消息接收者
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [[Cat alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    • 转发调用

    转发接收者失败,会进入转发调用流程,先返回一个转发方法签名,根据这个方法签名系统会创建一个NSInvocation对象,用于处理转发工作。

    // 返回方法签名:返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    // NSInvocation封装了一个方法调用,包括方法调用者、方法名、方法参数
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        [anInvocation invokeWithTarget:[[Cat alloc] init]];
    }
    

    消息转发过程如下:

    我们可以在forwardInvocation:方法中自定义任何逻辑,比如拦截过滤等。
    以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

    补充

    • 方法签名

    v16@0:8这样的类型编码作为返回值,可以不写数字:

    // 返回方法签名
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    

    也可以通过创建接收者实例对象找到对应的方法签名:

    // 返回方法签名
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [[[Cat alloc] init] methodSignatureForSelector:@selector(run)];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    • 类方法消息转发

    需要将消息转发方法换位类方法,并注意返回对象为类对象,转发接收者:

    // 转发接收者
    + (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [Cat class];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    转发调用:

    + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(run)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"......");
    }
    
    • invocation

    当消息转发流程进入forwardInvocation:方法后,可以利用invocation获取方法参数、获取方法返回值等数据。

    • @dynamic

    @dynamic就是通知编译器,不要给age自动生成setter和getter方法的实现,不自动生成员变量。(不影响setter/getter声明)

    @dynamic age;
    

    添加后调用person.age会报错,提示找不到方法。

    相关文章

      网友评论

          本文标题:iOS-浅谈OC中的消息机制

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