Runtime--Message、Message Forwarding

简介

Objective-C 程序能够在三个层次上和runtime系统交互:Objective-C Source Code、NSObject Methods、Runtime Functions。

Objective-C Source Code

此层次中,runtime函数将被自动调用。Runtime function的一个主要功能就是发送消息,如Messaging所示。

NSObject Methods

几乎所有的类都继承NSObject类,所以拥有其定义的方法。NSObject的方法定义了子类的表现方式,但是少数情况下,其只是定义了模板并未定义全部具体实现。例如实例方法description,默认事项打印类名和内存地址;子类可以重写以便可以返还更多描述信息(例如NSArray的实现)。

其中一些方法可以获取运行时信息,例如

//获取Class
+ (Class)class;
//继承链中的位置
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
//是否实现协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//是否可以相应方法
- (BOOL)respondsToSelector:(SEL)aSelector;
//获取方法实现
- (IMP)methodForSelector:(SEL)aSelector;

Runtime Functions

Runtime system是一动态共享库,提供一系列的函数接口和数据结构体在header文件中,路径为 /usr/include/objc。详情见 Objective-C Runtime Reference

Messaging

Objective-C直到运行期才去绑定方法的实现,编译器将方法调用转成消息发送objc_msgSend。例如[receiver message]将被转化成

//两个基本参数:receiver, selector
objc_msgSend(receiver, selector)

//如果带有参数
objc_msgSend(receiver, selector, arg1, arg2, ...)

objc_msgSend过程

其中objc_msgSend的动态绑定过程如下
1、首先根据selector找到Method的implementation。由于不同类可以实现同一个方法,所以根据参数receiver查找。
2、然后将receiver和参数传给selector。
3、最后返回值。
注:编译器自动调用objc_msgSend,你不应该在你写的代码中直接调用它。

消息发送的关键是编译器给每个类和对象生成的结构体,每个类的结构体包含两个重要部分:
1、父类的指针
2、方法调度表(dispatch table):每个类定义的方法的地址的入口

isa指针

每当生成一个新的对象的时候,内存alloc完毕,实例变量初始化完毕;其中第一个实例变量是isa指针,指向对象所属类的结构体,通过所属类可以找到整个继承链。

尽管严格来说isa不属于Objective-C,但是对于Objective-C runtime system来说是必须的;只要是继承NSObject类的都会自动生成isa变量,尽量不要生成自己的root class。

当一个对象调用方法时,objc_msgSend根据isa找到对象所属类,在其dispatch table中查找selector,如果没有则根据父类指针在其父类继续查找,根据继承关系链直到NSObject;一旦找到selector则直接调用。此过程在运行期完成,即所谓的动态绑定。

为了加速方法调用速度,runtime系统在每个类中开辟独立空间缓存了selector和addresses。在查找dispatch table之前先查找缓存(已经调用过的方法);如果selector在缓存中,其调用速度只比函数调用慢一点点。当程序运行足够时间以至于缓存了所有的方法,那么几乎所有的方法调用都在缓存中查找。其中缓存可以动态增加以收纳新的方法。

避开动态绑定

避开动态绑定的唯一方法是获取Method的address,想函数一样直接调用。使用于想要多次调用同一个方法,但是想减少时间开销的场景。可以通过以下方法获取address

//1、对象实例调用,aSelector为实例方法
//2、类调用,aSelector为类方法
- (IMP)methodForSelector:(SEL)aSelector;

然后通过指针调用方法,但是此指针必须转行成合适的函数指针,包括返回值和参数类型。
首先声明一个简单的Data类

@interface Data : NSObject

-(void)ivarMethod;
+(void)classMethod;

@end

大量的for循环最能体现节省时间了,以上的方法为了避免其他影响进行空实现。实例方法调用如下:

//创建对象
Data *data = [[Data alloc] init];

//方法调用
for (int i = 0; i < 1000000; i++)
{
   if (i == 0 || i == 999999)
   {
       NSLog(@"***分界线***");
   }
   [data ivarMethod];

}

//直接调用方法实现
void (*setter)(id, SEL)= (void (*)(id, SEL))[data methodForSelector:@selector(ivarMethod)];
for (int i = 0; i < 1000000; i++)
{
   if (i == 0 || i == 999999)
   {
       NSLog(@"***分界线***");
   }
   setter(self,@selector(ivarMethod));

}

//可以自行运行代码发现直接调用方法时间就是较短

类方法调用如下:

//方法调用
for (int i = 0; i < 1000000; i++)
{
   if (i == 0 || i == 999999)
   {
       NSLog(@"***分界线***");
   }
   [Data classMethod];

}

//直接调用方法实现
Class class_data = [Data class];

SEL sel_data = @selector(classMethod);

IMP imp_class = [class_data methodForSelector:sel_data];

void (*setter_classMethod)(id, SEL)= (void (*)(id, SEL))imp_class;

for (int i = 0; i < 1000000; i++)
{
   if (i == 0 || i == 999999)
   {
       NSLog(@"***分界线***");
   }
   setter_classMethod(class_data ,sel_data);

}

//可以自行运行代码发现直接调用方法时间就是较短

objc_msgSend调用

//必须转成合适的函数指针才能调用
void objc_msgSend(void /* id self, SEL op, ... */ )
//类方法调用
[Data classMethod];
((void(*)(id,SEL))objc_msgSend)([Data class],@selector(classMethod));

//实例方法调用
Data *data = [[Data alloc] init];
[data ivarMethod];
((void(*)(id,SEL))objc_msgSend)(data,@selector(ivarMethod));

//输出结果
调用classMethod
调用classMethod

调用ivarMethod
调用ivarMethod

Message Forwarding

如果向对象发送其不能处理的消息会报错,但是Runtime系统给予第二次机会处理未知消息。在介绍过程之前,先说明两个核心方法。

//方法1
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");

关于此方法说明:
1、此方法由基类NSObject声明实现;意味着所有类都可以使用。
2、默认实现调用doesNotRecognizeSelector:方法,然后抛出异常NSInvalidArgumentException,程序终止。
3、重新此方法,可以实现消息转发Message Forwarding。
4、对象如果想要响应非本身实现的方法,还必须重写methodSignatureForSelector:方法;因为消息转发机制需要使用获取method signature信息生成NSInvocation,然后调用方法1
5、此方法详细重写过程见下文。

//方法2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

关于此方法说明:
1、此方法由基类NSObject声明实现;意味着所有类都可以使用。
2、aSelector方面名;receiver是实例对象,方法是实例方法;receiver是类,方法是类方法。
3、此方法除了常用于实现protocols之外,还常用于创建NSInvocation对象的时候,例如消息转发message forwarding。
4、此方法详细重写过程见下文。

消息转发介绍

正常过程:如果对象调用自己未实现的方法(即发送未知消息),runtime调用对象的forwardInvocation:方法,NSObject的默认实现抛出异常,程序终止。
转发过程:如果对象调用自己未实现的方法(即发送未知消息),runtime调用对象的forwardInvocation:方法,对象重新此方法,自行处理未知消息。

重写过程

1、forwardInvocation:

方法的实现有两个任务:
1、确定可以相应消息的对象,不同的消息可以不同的对象
2、使用anInvocation转发消息、传递参数
例如

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

1、所有的返回值都会正常返回
2、此方法可以作为消息转发中心:处理未知消息然后转发;消息转换中心:转发所有消息到同一目标、A转成B、屏蔽(无响应无报错)、多种消息处理成一种响应

2、methodSignatureForSelector:

例如

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

下面举例说明,首先新建工程有ViewController类,然后新建Data类

//声明并实现方法
@interface Data : NSObject

-(void)ivarMethod;
@end

-(void)ivarMethod
{
    NSLog(@"调用ivarMethod");
}

ViewController中

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSString *sel = NSStringFromSelector(anInvocation.selector);
    NSLog(@"调用未知方法%@",sel);

    if ([sel isEqualToString:@"ivarMethod"])
    {
        [anInvocation invokeWithTarget:[[Data alloc] init]];

    }
    else
    {
        [super forwardInvocation:anInvocation];
    }
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];

     //生成方法签名
    if (! signature)
    {
        //处理不同的方法
        if (sel_isEqual(selector, @selector(ivarMethod)))
        {
            signature = [[[Data alloc] init] methodSignatureForSelector:selector];
        }

    }
    return signature;
}

调用方法

- (void)viewDidLoad 
{
[super viewDidLoad];

[(Data*)self ivarMethod];

}

//输出结果
调用未知方法ivarMethod
调用ivarMethod

消息转发与继承

尽管forwarding和继承很相似,但是NSObject从来不混淆两个概念。例如respondsToSelector:isKindOfClass:查找过程只在继承链中进行,从来不在转发链中进行。

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

大多数情况下返回NO,但是如果你构建了代理类或者拓展了某个类的功能那么此时转发机制需要像继承一样了,需要重写respondsToSelector:isKindOfClass:,例如

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

消息转发与多重继承

Objective-C本身不支持多重继承,但是消息转发可以实现类似效果。消息转发提供了多重继承大多数的功能,但是二者还是有本质区别的:多重继承吧不同的功能整合到一起,成变大趋势;消息转发,将功能分散到不同的对象中实现,成变小趋势(但是对调用层透明看不到)。
注意:消息转发不是被用来替换继承的。
参考文献:MessagingMessage ForwardingObjective-C Runtime Reference

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值