Runtime(二)消息发送和消息转发机制

2019-08-16  本文已影响0人  炒河粉儿

消息发送

[person read:book];
objc_msgSend(person, @selector(read:), book);

objc_msgSend的具体流程如下:

  1. 通过isa指针找到所属类
  2. 查找类的cache列表, 如果没有则下一步
  3. 查找类的”方法列表”
  4. 如果能找到与选择子名称相符的方法, 就跳至其实现代码
  5. 找不到, 就沿着继承体系继续向上查找
  6. 如果能找到与选择子名称相符的方法, 就跳至其实现代码
  7. 找不到, 执行”消息转发”.

原则就是从当前类开始查找方法的实现,找不到就去父类找,父类找不到就去父类的父类找,一直找到根类,如果最终还是没有找到,则执行消息转发。

消息转发

消息转发的整个流程为三步。

  1. 动态方法解析。
//实例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector;
//类方法
+ (BOOL)resolveClassMethod:(SEL)selector;
  1. 找备用接收者
- (id)forwardingTargetForSelector:(SEL)selector;
  1. 消息签名和消息转发。
//消息签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//消息转发
- (void)forwardInvocation:(NSInvocation *)invocation;

如果以上都无法处理消息的话,则会抛出异常。可以通过对下面方法的处理避免崩溃。

- (void)doesNotRecognizeSelector:(SEL)aSelector;
20160906224709111.png

代码实现例子

在Person类中定一个一个方法- (void)sendMessage:(NSString *)message;,但并没有去实现这个方法。

在Dog类中定义并实现了了- (void)sendMessage:(NSString *)message;

通过Person类的对象调用sendMessage:方法,来模拟实现消息的转发机制流程。

外部调用

Person *p = [[Person alloc]init];
[p sendMessage:@"哈哈"];

Person头文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

- (void)sendMessage:(NSString *)message;

@end

NS_ASSUME_NONNULL_END

Dog文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Dog : NSObject

- (void)sendMessage:(NSString *)message;

@end

NS_ASSUME_NONNULL_END



#import "Dog.h"

@implementation Dog

- (void)sendMessage:(NSString *)message
{
    NSLog(@"备用接收者实现:%@",message);
}
@end

Person.m文件中的实现。

当Person调用sendMessage方法时,因为person并没有实现这个方法,因此按照继承树层级查找查找不到该方法的实现。则会走第一个动态解析的步骤,在这一步,我们可以选择是否动态的去添加一下这个方法的实现,从而结束消息转发的流程,如果不解决这个问题,则会继续走下一步。

//1.动态方法解析
//是否要添加这个方法的实现,添加后这个方法就会实现,如果不添加则走第二步,越往后处理这个事件,消耗也是越来越大
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return class_addMethod([self class],sel, (IMP)sendMessage, "v@:@");
    }

    return NO;
}

void sendMessage(id self, SEL cmd, NSString *message){
    NSLog(@"%@",message);
}

当不在动态添加方法的情况下,消息转发继续想下一步执行,会去寻找备用者,也就是返回另一个类的对象,用这个对象去解决这个方法的实现问题。

//2.找备用的接收者
//转发到备用的接受者,让备用的接受者处理。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return [Dog new];
    }
    //如果没有找到备用者,就走继承树
    return [super forwardingTargetForSelector:aSelector];
}

当找不到备用接收者的时候,则会进入到第三步,第三步分为两个小步,第一步先进行方法签名,第二步进行消息转发,将消息转发给其他对象。

//3. 1.方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

//3. 2.消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = [anInvocation selector];
    Dog *dog = [Dog new];
    //如果dog实现了这个方法,则把这个消息转发给dog,如果没实现,则走继承树
    if ([dog respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:dog];
    }else {
        [super forwardInvocation:anInvocation];
    }
    
    [super forwardInvocation:anInvocation];
}

如果以上都无法处理这个消息,则消息无法处理,会产生崩溃,此时我们重写下面方法,则会解决崩溃问题。

// 消息无法处理的情况 处理过后不会崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"消息无法处理");
    
}
上一篇下一篇

猜你喜欢

热点阅读