Objective-C学习: OC方法调用的本质

文章目录

  • [OC 方法调用的本质是 消息的发送](#OC 方法调用的本质是 消息的发送)
  • 实例对象(instance)
  • 四、类对象(Class)
  • 元类对象(meta-class)
  • [SEL 是什么?](#SEL 是什么?)
  • [IMP 是什么?](#IMP 是什么?)
  • [Method 是什么?](#Method 是什么?)
  • 完整调用流程
  • 一、从一行代码开始(所有一切的起点)
  • [二、Step 0:selector table(方法名 → SEL)](#二、Step 0:selector table(方法名 → SEL))
  • [三、Step 1:变成消息发送](#三、Step 1:变成消息发送)
  • [四、Step 2:找到类(isa)](#四、Step 2:找到类(isa))
  • [五、Step 3:先查 cache(最快)](#五、Step 3:先查 cache(最快))
  • [六、Step 4:查 method list(真正数据源)](#六、Step 4:查 method list(真正数据源))
  • [七、Step 5:查父类(继承链)](#七、Step 5:查父类(继承链))
  • [八、Step 6:还找不到 → runtime 动态流程(你刚刚那张图)](#八、Step 6:还找不到 → runtime 动态流程(你刚刚那张图))
    • [① 动态方法解析](#① 动态方法解析)
    • [② 快速转发](#② 快速转发)
    • [③ 完整消息转发](#③ 完整消息转发)
    • [④ 还不行 → 崩溃](#④ 还不行 → 崩溃)
  • [九、Step 7:最终执行 IMP(本质)](#九、Step 7:最终执行 IMP(本质))

OC 方法调用的本质是 消息的发送

OC 中对象本身只存储实例变量, (成员变量) 和 一个 isa 指针, 不存方法, 方法都存储在类对象中

实例对象(instance)

复制代码
instance
├── isa → Class
└── ivars(成员变量)

特点:

  • 存储数据(属性、本质是 ivar)

    ivar(instance variable)是实例变量,用于在对象中存储数据,是属性(property)的底层实现。

    property = ivar + getter/setter 方法

  • 不存方法

  • 每个对象一份

    Person *p = [[Person alloc] init];

四、类对象(Class)

复制代码
Class
├── isa → meta-class
├── superclass → 父类
├── cache(方法缓存)
├── method list(实例方法)
└── ivar list(成员变量信息)

存什么?

  • 实例方法(- 方法)
  • 属性信息
  • 协议等

所有实例共享同一个 Class

元类对象(meta-class)

复制代码
meta-class
├── isa → 根元类
├── superclass → 父类的 meta-class
├── cache
└── method list(类方法)

存什么?

类方法(+ 方法)

复制代码
+ (void)test;

和类对象一样, 一个类对象只有一个元类对象

SEL 是什么?

复制代码
SEL sel = @selector(test);

本质:

SEL 是一个方法名的唯一标识(类似 key)

  • 底层是一个字符串(但做了唯一化)

  • 相同方法名 → 同一个 SEL

    @selector(test)

本质类似:

复制代码
"test"

但 runtime 会优化成唯一 ID(提高查找效率)


IMP 是什么?

复制代码
typedef id (*IMP)(id, SEL, ...);

本质:函数指针

  • 指向方法真正的实现代码
  • 是最终被 CPU 执行的东西

Method 是什么?

运行时里还有一个结构:

复制代码
struct method_t {
    SEL name;   // 方法名
    IMP imp;    // 方法实现
};

可以理解为一张表:

SEL(方法名) IMP(函数地址)
test 0x123456

完整调用流程

Objective-C 的方法调用,本质是 runtime 通过 isa 找到类,利用 SELcache → method list → 父类链 中查找对应的 IMP,命中后执行函数;若未找到,则进入动态方法解析与消息转发流程。

一、从一行代码开始(所有一切的起点)

复制代码
[obj run];

编译器会做两件事


二、Step 0:selector table(方法名 → SEL)

复制代码
@selector(run)

会被注册到一张全局表:selector table

复制代码
"run" → SEL(0x1111)

同一个方法名 → 永远同一个 SEL

本质:把字符串变成唯一指针(加速比较)


三、Step 1:变成消息发送

复制代码
objc_msgSend(obj, SEL)

OC 没有"直接调用方法",全是发消息


四、Step 2:找到类(isa)

复制代码
obj → isa → Class

对象只存:

  • ivar(数据)
  • isa(指向类)

方法都在类里


五、Step 3:先查 cache(最快)

复制代码
Class.cache:

SEL → IMP

如果有:

复制代码
run → 0x123456

直接执行 IMP(结束)


六、Step 4:查 method list(真正数据源)

复制代码
Class.method list:

SEL → IMP 本质结构:

struct method_t {
    SEL name;
    IMP imp;
};

找到后:

拿到 IMP

写入 cache(优化)


七、Step 5:查父类(继承链)

复制代码
Class → superclass → superclass → ...

每一层都:

复制代码
cache → method list

八、Step 6:还找不到 → runtime 动态流程(你刚刚那张图)


① 动态方法解析

复制代码
+ (BOOL)resolveInstanceMethod:(SEL)sel

你可以动态加方法(IMP)


② 快速转发

复制代码
- (id)forwardingTargetForSelector:(SEL)aSelector;

换对象处理:

复制代码
return otherObj;

③ 完整消息转发

复制代码
- methodSignatureForSelector
- forwardInvocation

你自己完全接管调用


④ 还不行 → 崩溃

复制代码
unrecognized selector sent to instance

九、Step 7:最终执行 IMP(本质)

复制代码
imp(obj, SEL);

👉 IMP 本质是函数指针:

复制代码
void run(id self, SEL _cmd) {
    // 方法实现
}
相关推荐
CodeStats1 分钟前
《源纹天书》第121-125章:源匠归来——全栈重构与归元圣域的2.0时代
java·开发语言·源纹天书
binbin_522 分钟前
UIAbility 与 WindowStage:窗口创建、加载、销毁的完整链路
开发语言·javascript·深度学习·华为·harmonyos
AI人工智能+电脑小能手2 分钟前
【大白话说Java面试题 第154题】【06_Spring篇】第14题:Spring 支持的 Bean 作用域
java·开发语言·spring·面试
星夜夏空9913 分钟前
C++学习(1) ——C与C++
c语言·c++·学习
旖-旎20 分钟前
QT界面优化(6)
开发语言·c++·qt
AI科技星20 分钟前
基于超复数广义分形流形的电磁耦合与缪子反常磁矩几何理论
开发语言·平面·重构·概率论·量子计算·乖乖数学·全域数学
24计网1王仔寿23 分钟前
Linux 系统运维全栈学习路线|从 Shell 脚本到容器云 OpenStack 完整学习指南
linux·学习·openstack
组合缺一25 分钟前
用 ChatModel 构建 LLM 驱动的 Java 应用
java·开发语言·ai·llm·solon·rag
零点零一40 分钟前
QT 5升级到 Qt 6 使用 Clazy 检查将 C++ 应用程序移植到 Qt 6
开发语言·c++·qt
caimouse1 小时前
reactos 测试安装32位微信失败的日志
开发语言·微信