OC底层原理:objc_msgSend详解之动态解析和消息转发

235 阅读3分钟

一、消息发送

回顾前一篇文章,我们总结了objc_msgSend第一阶段~消息发送的流程图,如下:

image.png

二、动态解析

2.1 动态解析流程探索

  • 判断是否有动态解析LOOKUP_RESOLVER标记

image.png

  • 判断是不是Meta-Class,非元类调用resolveInstanceMethod、元类调用resolveClassMethod

image.png

  • 判断该方法是否已经被标记有动态解析 behavior,标记后receiverClasscache中也会缓存标记

image.png

image.png

2.2 动态解析流程代码验证

2.2.1 消息发送流程代码实例

receiver(person)通过isa找到receiverClass(ZGPerson.class),在receiverClassclass_rw_t中查找方法test,查找到test方法,调用方法,结束查找 并将方法缓存到reveiverClasscache中。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZGPerson : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

#import "ZGPerson.h"

@implementation ZGPerson

- (void)test {
    NSLog(@"%s",__func__);
}

@end

#import <Foundation/Foundation.h>

#import "ZGPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZGPerson *person = [ZGPerson new];
        [person test];
    }
    return 0;
}

image.png

2.2.2 动态解析流程代码实例

  • 屏蔽掉- (void)test的实现,因为它是实例方法,并且ZGPerson不是Meta-class,所以我们调用resolveInstanceMethod方法来进入动态解析阶段。
#import "ZGPerson.h"

@implementation ZGPerson

//- (void)test {
//    NSLog(@"%s",__func__);
//}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s",__func__);
    return [super resolveInstanceMethod:sel];
}

@end
  • 在动态解析阶段,我们首先调用父类方法,什么操作都不做,看一下确实执行并调用了resolveInstanceMethod方法,但是因为动态解析阶段没有查找到对应的方法,产生崩溃。

image.png

  • 动态添加方法,进入动态解析阶段,调用resolveInstanceMethod方法,该方法中将test的实现动态替换为other,所以我们查找到other方法,调用方法,结束查找并将方法缓存到reveiverClasscache中。
#import <objc/runtime.h>

@implementation ZGPerson

//- (void)test {
//    NSLog(@"%s",__func__);
//}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
       //获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));
        //动态添加test方法实现
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        //告诉系统有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)other {
    NSLog(@"%s",__func__);
}

@end

image.png

2.3 动态解析流程图

通过前面部分源码流程探索和代码验证,我们可以得到以下动态解析的流程图:

image.png

三、消息转发

3.1 消息转发流程探索

  • log_and_fill_cache ==> objcMsgLogEnabled

image.png

image.png

  • logMessageSend,暂存数据地址/tmp/msgSends-%d

image.png

  • instrumentObjcMessageSends

image.png

  • 实例代码添加instrumentObjcMessageSends方法配合验证

image.png

  • 在电脑上前往文件夹/tmp找到msgSends文件

image.png

  • 方法调用堆栈 forwardingTargetForSelector:methodSignatureForSelector:就是我们消息转发调用的方法

image.png

3.2 消息转发流程代码验证

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZGPerson : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

#import "ZGPerson.h"

#import <objc/runtime.h>
#import "ZGCat.h"

@implementation ZGPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}

//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
//    anInvocation.target = [ZGCat new];
//    [anInvocation invoke];
    [anInvocation invokeWithTarget:ZGCat.new];
}

@end

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZGCat : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

#import "ZGCat.h"

@implementation ZGCat

- (void)test {
    NSLog(@"%s",__func__);
}

@end

#import <Foundation/Foundation.h>

#import "ZGPerson.h"
#import "ZGCat.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZGPerson *person = [ZGPerson new];
        [person test];
        
    }
    return 0;
}

image.png

3.3 消息转发流程图

通过前面部分源码流程探索和代码验证,我们可以得到以下消息转发的流程图:

image.png