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) {
    // 方法实现
}
相关推荐
WeirdoPrincess2 小时前
iOS 打包签名资料准备指南(HBuilderX / uni-app)
ios·uni-app
Monkey-旭2 小时前
Java HTTP证书全用法详解:原理、配置、实战与问题排查
java·开发语言·http·证书·ssl
m0_730115112 小时前
C++与Python混合编程实战
开发语言·c++·算法
LSL666_2 小时前
IService——删除
java·开发语言·mybatisplus·iservice
前端小趴菜~时倾3 小时前
自我提升-python爬虫学习:day04
爬虫·python·学习
小罗和阿泽3 小时前
接口测试系列 接口自动化测试 pytest框架(三)
开发语言·python·pytest
毕设源码-邱学长9 小时前
【开题答辩全过程】以 基于Java的学校住宿管理系统的设计与实现为例,包含答辩的问题和答案
java·开发语言
rookieﻬ°10 小时前
PHP框架漏洞
开发语言·php
淮北49410 小时前
vim学习进阶
学习·编辑器·vim