美文网首页
iOS消息机制消息转发详解

iOS消息机制消息转发详解

作者: 一生随愿为剑客 | 来源:发表于2019-10-23 01:19 被阅读0次

引言

Objective-C 是一门动态语言,在运行时我们才知道具体的对象和方法。在Objective-C 中,动态性是由 runtime 相关的库赋予的。runtime维护者程序运行的相关机制,其中最重要的就是消息机制。

消息发送

在iOS中,方法的调用就是消息发送的过程。即调用

objc_msgSend(void /* id self, SEL op, ... */ )。

我们先来看一下SEL的定义:

typedef struct objc_selector *SEL;

objc_selector是一个映射到方法的C字符串。@selector()选择子只与函数名有关。这也是OC不支持函数重载的原因。因为即使变量不同也会导致他们具有相同的选择器。 -- 有点扯远了,当个小知识点来看吧。

SEL能够找寻到方法地址,来实现调用,调用过程是什么样呢,我们首先来了解一下Objective-C 的类结构。

在Objective-C中,每个对象都有一个isa指针,指向该对象的类,类中描述了该对象的特点,如成员变量的列表、成员函数的列表等。例如NSObject就是一个包含isa指针的结构体。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

同样的,在Objective-C中,类同样接收消息,所以每个类实际上也是一个对象我们称之为类对象,每个类也有它的isa指针。

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

由于类也是一个对象,所以它也必须是一个类的实例,这个类就是元类(metaclass)。元类中保存了类方法的列表。

我们来看下经典的结构图:

由此得知:

  1. 类对象中定义了对象的实例方法,元类中定义了类方法。
  2. 对象方法的调用先从该类对象中方法列表中开始,没找到的话就会继续从该类对象的父类中查找。当然了,由于效率的问题,每个消息都遍历一次objc_method_list并不合理。所以需要把经常被调用的函数缓存下来,去提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情,把sel的method_name作为key,method_imp作为value给存起来。当再次收到消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。同样的,类方法的调用会先从该元类中查找,没找到的话就会从该元类的父类中查找。

消息转发

在调用对象拿到对应的selector之后,如果自己无法执行这个方法,那么该条消息要被转发。或者临时动态的添加方法实现。如果转发到最后依旧没法处理,程序就会崩溃。

如以下例子:

新建一个Person类继承于NSObject,并声明一个msgTest方法(不实现);

@interface Person : NSObject

- (void)msgTest;

@end

调用该方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    Person * p = [Person new];
    [p msgTest];
}

此时我们将项目跑起来就会发现,项目是能通过编译的,但是会崩溃掉

-[Person msgTest]: unrecognized selector sent to instance 0x6000020543e0

在方法在调用时,系统会查看这个对象能否接收这个消息(没有实现这个方法),如果不能接收,就会调用下面这几个方法,会采用拯救模式,给你“补救”的机会。

第一次补救: 动态方法解析


/*

cls:要添加方法的类
name:选择器
imp:方法实现,IMP在objc.h中的定义是:typedef id (*IMP)(id, SEL, ...);该方法至少有两个参数,self(id)和_cmd(SEL)
types:方法,参数和返回值的描述,"v@:"表示返回值为void,没有参数
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(msgTest)){
        return   class_addMethod([self class],sel, (IMP)reTest, "v@:");
  }
    return [super resolveInstanceMethod:sel];
}

void reTest(id self, SEL _cmd) {
    NSLog(@"test");
}

可看到打印数据:

2019-10-22 22:10:17.526103+0800 learn[47237:860941] test

注: resolveInstanceMethod处理对象方法,resolveClassMethod处理类方法。

第二次补救: 消息重定向

我们继续以实例方法举例:

创建一个新的类RePerson,该类包含有msgTest的实现方法。

#import "RePerson.h"

@implementation RePerson

- (void)msgTest{
    NSLog(@"rePerson");
}

@end

我们在Person类里实现两个步骤:

  1. resolveInstanceMethod返回值设为NO。

2.forwardingTargetForSelector返回值为RePerson对象。

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(msgTest)){
        return   NO;
    }
    return [super resolveInstanceMethod:sel];
}


- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(msgTest)){
        return  [RePerson new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

这样就可以得到结果:

2019-10-23 00:56:07.855455+0800 learn[47519:906599] rePerson

第三次补救: 消息转发

也是改变调用对象,使该消息在新对象上调用;不同是forwardInvocation方法带有一个NSInvocation对象,这个对象保存了这个方法调用的所有信息,包括SEL,参数和返回值描述等。

同样的,我们利用上文中描述的RePerson类,实现以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

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

    if (anInvocation.selector == @selector(msgTest)){
        [anInvocation invokeWithTarget:[RePerson new]];
        return;
    }
    [super forwardInvocation:anInvocation];
}

同样的,我们也可以拿到如下答案:

2019-10-23 01:09:16.310219+0800 learn[47574:911006] rePerson

相关文章

  • runtime系列文章总结

    《iOS Runtime详解(消息机制,类元对象,缓存机制,消息转发)》《消息转发机制与Aspects源码解析》《...

  • iOS 消息发送与转发详解

    iOS 消息发送与转发详解 iOS 消息发送与转发详解

  • iOS消息机制消息转发详解

    引言 Objective-C 是一门动态语言,在运行时我们才知道具体的对象和方法。在Objective-C 中,动...

  • iOS消息转发机制详解

    前言 之前对于iOS的消息转发机制只是有个大致的了解,最近刚好有空,总结下相关的知识点。 1、函数的调用方式 Ob...

  • iOS理解Objective-C中消息转发机制附Demo

    iOS理解Objective-C中消息转发机制附Demo iOS理解Objective-C中消息转发机制附Demo

  • iOS的消息转发机制详解

    iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添...

  • Runtime

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

  • iOS面试-基础

    [toc] Runloop AutoReleasePool 多线程 响应者链 消息响应机制 消息转发机制 iOS内...

  • iOS面试题总结(二)

    iOS面试题(二) 消息发送和转发机制,SEL和IMP 消息发送转载自黄龙辉消息发送和消息转发机制 在Object...

  • iOS - 消息转发机制

    我们知道,OC是动态语言,所有的方法都会以消息的形式传递给对象,对象会根据方法的类型来进行实例方法或者类方法的选择...

网友评论

      本文标题:iOS消息机制消息转发详解

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