OC底层原理:objc_msgSend详解

145 阅读3分钟

一、将Objective-C代码转换为C\C++代码

  • 给项目加一个新的target

image.png

  • New Run Script Phase

image.png

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

image.png

  • 如果需要链接其他框架,使用-framework参数。比如-framework UIKit

  • 为了方便查看,将生成的main.cpp文件加入项目中,为了避免文件冲突报错,取消参与编译的勾选项

image.png

二、OC方法调用本质

#import <Foundation/Foundation.h>

#import "ZGPerson.h"

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

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZGPerson : NSObject

@property (nonatomic, assign) int age;
- (void)learn;
@end

NS_ASSUME_NONNULL_EN

#import "ZGPerson.h"

@implementation ZGPerson

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

@end

2.1 代码反编译为C++

  • 将项目代码编译成C++文件,然后拉取到最后,截图如下:

image.png

  • 去掉强转条件,实际作用代码如下图:

image.png

由此我们可以得出objc_msgSend的参数:

第一个参数:消息的接收者

第二个参数:消息的方法名(sel)

结论:OC中的方法调用,其实都是转换为objc_msgSend函数的调用。

2.2 objc_msgSend和它的相关类

拖动main.cpp文件到代码顶部,我们看到objc_msgSend有以下几个相关类:

image.png

下面环节,我们来着重了解一下objc_msgSendSuperobjc_msgSend的区别。

三、objc_msgSendSuperobjc_msgSend区别

ZGPerson添加一个ZGCat子类,在main.m文件中,初始化ZGCat类,并调用父类属性age

#import "ZGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface ZGCat : ZGPerson

@end

NS_ASSUME_NONNULL_END

#import "ZGCat.h"

@implementation ZGCat

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@\n%@",[self class],[super class]);
        
    }
    return self;
}
@end

#import <Foundation/Foundation.h>

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZGCat *cat = [ZGCat new];
        cat.age = 10;
    }
    return 0;
}

打印结果:

image.png

这里ZGCat.m[self class],[super class]打印结果是一致的,都是ZGCat,那么这里我们的selfsuper有什么区别哪?这就是我们将要探讨的内容。

3.1 代码反编译为C++

为了想要知道ZGCat.m文件中发生了什么,我们将它反编译成C++文件(步骤同上,不再赘述)。

  • 打开ZGCat.cpp文件,找到对应的init方法

image.png

  • 去掉强转条件,实际作用代码如下图:

image.png

objc_msgSend第一个参数为self(消息的接收者),第二个参数为消息的方法名(sel)

objc_msgSendSuper的第一个参数为__rw_objc_super类型的结构体,结构体包含两个参数:(第一个参数为self(消息的接收者),第二个参数为消息的方法名(sel))。

因为消息的接收者没有改变,所以[super class]打印出来的一样是self,这个对象所指的类,也就是ZGCat

结论: objc_msgSend是给本类发消息,objc_msgSendSuper是给父类发消息,结果相同,但出发点不同。

3.2 自己实现一个objc_msgSendSuper

  • 我们看到[super class]的第一个参数变成了一个_rw_objc_super结构体,我们在源码里看一下这个结构体对应的内部结构。

image.png

  • 源码中查看objc_msgSendSuper结构

image.png

  • 具体实现代码
- (void)learn {
//    [super learn];
    struct objc_super zg_objc_super;
    zg_objc_super.receiver = self;
    zg_objc_super.super_class = ZGPerson.class;
    //格式强转
    void* (*learnSuper)(struct objc_super * self, SEL _cmd) = (void *)objc_msgSendSuper;
    //结构注入参数
    learnSuper(&zg_objc_super, @selector(learn));
    
}

image.png

四、objc_msgSend源码解读

在OC源码中查找objc_msgSend,我们主要看它在arm64中的源码实现:

image.png

  • 消息接收者receiver为空,跳出

image.png

等同于以下代码:

void objc_msgSend(id receiver, SEL selector) {
    if (receiver == nil) return;
}
  • 实例对象isa找到class

image.png

  • 查找缓存CacheLookup

image.png

image.png

  • 缓存查找或者没有缓存查找方法_objc_msgSend_uncached

image.png

image.png

  • MethodTableLookup

image.png

  • lookUpImpOrForward

image.png

image.png

  • getMethodNoSuper_nolock -> search_method_list_inline -> findMethodInSortedMethodList 里的慢速查找算法

image.png

五、objc_msgSend执行流程01~消息发送

总结上文部分,我们得到objc_msgSend第一阶段消息发送的流程图:

image.png

下文我们会详细阐述objc_msgSend的后两个阶段。