美文网首页
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中的消息机制

    目录 简介消息发送objc_msgSend动态方法解析消息转发---- 转发接收者---- 转发调用补充---- ...

  • OC中的消息机制和动态运行时

    消息机制:OC中的实例对象调用一个方法称作消息传递 OC中里的消息传递采用动态绑定机制来决定具体调用哪个方法,OC...

  • iOS runtime

    runtime 是 oc 语音的基础首先runtime的核心机制是消息机制 也就是oc的消息机制首先oc的消息机制...

  • OC-Runtime-Class结构和OC消息机制

    OC - Runtime - Class 结构 和 OC 消息机制 Runtime 源码中 Class 结构如下:...

  • OC中如何避开消息机制

    OC中的消息机制可概括为三步: 消息发送。 动态解析。 消息转发。 由于消息机制objc_msgSend()方法调...

  • OC消息机制解析

    消息机制是OC Runtime的一个重要机制 OC中的对象在调用方法时,如[myObj testMethod:ar...

  • OC中消息转发机制

    在编译期 消息传递的过程中向类发送了其无法解读的消息并不会报错,因为在运行期可以继续向类中添加方法,所以编译...

  • 理解OC中的消息机制

    OC中在对象上调用方法其实就是给该对象发送一个消息,比如 例子中,可以理解为给student对象发送一个消息,其中...

  • OC中的消息转发机制

    在本文中,将为你解释在OC的动态机制中,一个对象是如何调用,并且在对象中找不到方法的情况下,如何将方法通过"发消息...

  • iOS开发 — 初识消息机制

    消息机制原理 iOS进程是一个活的循环(runtime), OC中调用方法的实质就是发送消息, 而消息机制的本质就...

网友评论

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

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