手写 Swift 运行时:objc_msgSend 的汇编级解析

专栏 :手写框架系列
编号 :D02 · 系列第 2 篇
字数 :约 6000 字
标签:Swift / iOS / 运行时 / objc_msgSend / 汇编 / 方法分发 / 缓存查找


前言

上篇文章我们实现了完整的引用计数系统。今天我们深入 Swift(和 Objective-C)运行时的核心:方法分发

当你在 Swift 中调用 object.doSomething() 时,底层到底发生了什么?

答案就在 objc_msgSend------ Objective-C 的消息分发函数。本篇文章我们将:

  1. 阅读真实的 objc_msgSend 汇编源码
  2. 手写简化版的消息分发逻辑
  3. 理解缓存查找的原理
  4. 对比 Swift 和 Objective-C 的方法分发差异

一、方法分发的基本概念

1.1 三种方法分发机制

scss 复制代码
┌──────────────────────────────────────────────────────────────┐
│                   方法分发(Method Dispatch)                   │
├────────────────┬──────────────────┬─────────────────────────┤
│  直接调用       │   虚表调度       │   消息传递            │
│  (Direct)      │   (Virtual Table)│   (Message Passing)   │
├────────────────┼──────────────────┼─────────────────────────┤
│ 静态、非虚函数  │  类继承层次结构   │  运行时查找           │
│  编译时决定地址 │  运行时查表决定  │  支持动态替换         │
├────────────────┼──────────────────┼─────────────────────────┤
│ C 函数         │ C++ 虚函数       │ Objective-C           │
│ 非虚 Swift 方法 │ Swift 类的继承方法 │ 全部方法              │
│ 性能最优       │ 中等性能         │ 有查找开销             │
└────────────────┴──────────────────┴─────────────────────────┘

1.2 Swift 的方法分发策略

Swift 根据上下文选择不同的分发策略:

swift 复制代码
class MyClass {
    // 编译时直接分发(final)
    final func method1() { }          // 直接调用,零开销

    // 虚表分发(class)
    class func method2() { }          // 运行时查表

    // 消息传递(dynamic / @objc)
    @objc dynamic func method3() { }  // objc_msgSend
    dynamic func method4() { }         // Swift 6 中 dynamic 隐含 @objc
}

// 协议方法也是消息传递
protocol MyProtocol {
    func method()  // 调用时通过 vtable 或消息传递
}

struct MyStruct: MyProtocol {
    func method() { }  // 静态分发(值类型不支持继承)
}

二、objc_msgSend 的汇编级分析

2.1 为什么 objc_msgSend 用汇编实现

objc_msgSend 必须用汇编写,原因很直接:

  1. 参数传递:它不知道参数类型和数量,无法用 C 函数表达
  2. 返回值的寄存器处理:返回值可能通过不同的寄存器返回(整数 vs 浮点数 vs 结构体)
  3. 零开销:作为最频繁调用的函数,不能有任何额外开销

2.2 arm64 架构上的 objc_msgSend

Apple Silicon 上的实现(简化版):

asm 复制代码
; objc_msgSend 入口
; x0 = receiver (self)
; x1 = selector (_cmd)

objc_msgSend:
    ; 检查 receiver 是否为 nil
    cbz x0, LNilOrCache    ; if x0 == 0, jump

    ; 尝试从缓存查找 IMP
LGetCacheSave:
    ldr x13, [x0, #OBJECT_METACLASS]  ; x13 = obj-> ISA
    and x13, x13, #CACHE_MASK          ; x13 = class + mask -> cache bucket
    ldp x12, x13, [x13], #SEL_SHIFT   ; x12 = bucket[0].sel, x13 = bucket[0].imp
    cmp x12, x1                         ; compare selector
    b.eq  LCacheHit                     ; if equal, cache hit!

    ; 缓存未命中,查方法列表
LCacheMiss:
    ldr x16, [x0, #OBJECT_CLASS]       ; x16 = class
    ldr x16, [x16, #CLASS_DATA_OFFSET] ; x16 = class->data (class_rw_t)
    ldr x16, [x16, #METHODS_OFFSET]   ; x16 = class->data->methods

    ; 在方法列表中线性搜索
LMethodSearch:
    cmp x16, #0                         ; if methods == nil, fail
    beq LMethodFail

    ldr w17, [x16, #METHOD_COUNT]      ; w17 = method_array.count
    cbz w17, LMethodFail               ; if count == 0, fail

    mov x15, x16                        ; x15 = method_array base
    mov x14, #0                         ; x14 = index = 0

LMethodLoop:
    ldr x12, [x15], #METHOD_SEL_OFFSET ; x12 = method[i].sel, x15 += 8
    cmp x12, x1                         ; compare selector
    b.eq  LMethodFound                  ; found!

    add x14, x14, #1                   ; index++
    cmp x14, w17                        ; index < count ?
    b.lt  LMethodLoop                   ; continue

LMethodFail:
    ; 调用转发机制(_objc_msgForward)
    b   _objc_msgForward

LMethodFound:
    ; 找到方法,获取 IMP
    ldr x11, [x15, #METHOD_IMP_OFFSET] ; x11 = method.IMP
    ; 缓存 IMP
    bl  _cache_fill

    ; 调用 IMP(跳转到方法实现)
LCacheHit:
LInvoke:
    br x11                               ; jump to IMP

LNilOrCache:
    ; nil receiver:返回 nil(C 函数)或 0(基础类型)
    ret

2.3 关键数据结构

c 复制代码
// Objective-C 对象的内存布局
struct objc_object {
    Class isa;  // 指向类对象(MetaClass)
};

// 类的内存布局
struct objc_class {
    Class isa;                    // 元类指针
    Class superclass;             // 父类
    cache_t cache;               // 方法缓存
    class_data_bits_t bits;      // 类数据(包含方法列表)
};

// 缓存结构
struct cache_t {
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    struct bucket_t *_buckets;
    uint16_t _maybeMask;
    uint16_t _flags;
};

// 缓存桶
struct bucket_t {
    SEL _sel;
    IMP _imp;
};

2.4 缓存查找的复杂度

c 复制代码
// 简化的缓存查找算法
static inline ALWAYS_INLINE cache_t *
getCache( objc_class *cls, SEL sel)
{
    // 使用类的掩码计算哈希
    mask_t scan = cls->cache.mask;
    mask_t position = sel & scan;

    // 线性探测
    while (true) {
        bucket_t *bucket = &cls->cache.buckets[position];

        if (bucket->sel() == 0) {
            return NULL;  // 空槽,缓存未命中
        }
        if (bucket->sel() == sel) {
            return bucket;  // 命中!
        }
        // 哈希冲突,向下探测
        position = (position + 1) & scan;
    }
}

三、手写简化版消息分发器

3.1 完整实现

c 复制代码
// message.h
// 简化版消息传递系统,模拟 objc_msgSend 的核心逻辑

#ifndef MESSAGE_H
#define MESSAGE_H

#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>

// ================= 类型定义 =================

typedef struct objc_object {
    struct objc_class *isa;
} *id;

typedef struct objc_class {
    struct objc_class *isa;          // 元类
    struct objc_class *superclass;   // 父类
    struct method_list_t *methods;    // 方法列表
    struct cache_t *cache;            // 方法缓存
    const char *name;                // 类名
} *Class;

typedef struct method_t {
    const char *name;                // SEL(选择器)
    const char *types;               // 方法类型编码
    void (*imp)(void);               // IMP(方法实现)
} *Method;

typedef struct cache_t {
    void **buckets;
    uint32_t capacity;
    uint32_t count;
    uint32_t mask;                   // capacity - 1(用于哈希)
} *Cache;

typedef struct method_list_t {
    uint32_t count;
    Method methods[];
} *MethodList;

// ================= 方法 =================

// 发送消息(模拟 objc_msgSend)
void *objc_msgSend(id obj, const char *selector, ...);

// 类注册和方法添加
Class objc_registerClass(const char *name, Class superclass);
void objc_addMethod(Class cls, const char *name, const char *types, void (*imp)(void));
id objc_msgSendSuper(struct objc_super *super, const char *selector, ...);

// ================= 内部函数 =================
static inline Cache cache_create(uint32_t capacity);
static inline void cache_fill(Cache cache, Method method);
static inline Method cache_lookup(Cache cache, const char *sel);
static Method class_getMethod(Class cls, const char *sel);

#endif
c 复制代码
// message.c

#include "message.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

// ================= 工具函数 =================

static inline uint32_t hash_selector(const char *sel, uint32_t mask) {
    uint32_t h = 0;
    while (*sel) {
        h = h * 31 + *sel++;
    }
    return h & mask;
}

static inline bool selector_equal(const char *a, const char *b) {
    return strcmp(a, b) == 0;
}

// ================= 缓存操作 =================

static inline Cache cache_create(uint32_t capacity) {
    Cache cache = calloc(1, sizeof(struct cache_t));
    cache->capacity = capacity;
    cache->mask = capacity - 1;
    cache->buckets = calloc(capacity, sizeof(void *));
    cache->count = 0;
    return cache;
}

static inline void cache_fill(Cache cache, Method method) {
    if (!cache || !method) return;

    uint32_t index = hash_selector(method->name, cache->mask);
    uint32_t original = index;

    // 线性探测
    while (cache->buckets[index] != NULL) {
        index = (index + 1) & cache->mask;
        if (index == original) return;  // 缓存满
    }

    cache->buckets[index] = method->imp;
    cache->count++;

    printf("[CACHE FILL] selector=%s -> imp=%p (index=%u)\n",
           method->name, (void *)method->imp, index);
}

static inline Method cache_lookup(Cache cache, const char *sel) {
    if (!cache || !sel) return NULL;

    uint32_t index = hash_selector(sel, cache->mask);
    uint32_t original = index;
    int probes = 0;

    while (cache->buckets[index] != NULL) {
        Method method = (Method)cache->buckets[index];
        // 注意:这里我们只存了 IMP,需要额外的表存 selector
        // 真实实现在 bucket_t 中同时存 sel 和 imp
        index = (index + 1) & cache->mask;
        probes++;

        if (probes > cache->capacity) break;  // 防止无限循环
    }

    return NULL;  // 简化版:返回 NULL 表示未命中
}

// ================= 方法列表查找 =================

static Method class_getMethod(Class cls, const char *sel) {
    if (!cls) return NULL;

    Method result = NULL;

    // 从当前类开始,向上遍历继承链
    Class current = cls;
    while (current) {
        if (current->methods) {
            for (uint32_t i = 0; i < current->methods->count; i++) {
                Method m = &current->methods->methods[i];
                if (selector_equal(m->name, sel)) {
                    result = m;
                    goto done;
                }
            }
        }
        current = current->superclass;
    }

done:
    if (result) {
        printf("[METHOD LOOKUP] Found '%s' in class '%s'\n", sel, cls->name);
    } else {
        printf("[METHOD LOOKUP] NOT FOUND: '%s'\n", sel);
    }
    return result;
}

// ================= objc_msgSend 核心实现 =================

void *objc_msgSend(id obj, const char *selector, ...) {
    if (!obj || !selector) {
        printf("[MSGSEND] obj=%p, selector=%s -> NIL\n", (void *)obj, selector ? selector : "(null)");
        return NULL;
    }

    Class cls = obj->isa;

    printf("[MSGSEND] obj=%p, isa=%s, selector=%s\n",
           (void *)obj, cls->name, selector);

    // 1. 尝试从缓存查找
    Method method = NULL;
    if (cls->cache) {
        // 注意:简化版缓存只返回 IMP,实际需要比较 selector
        // 这里用简化版:缓存未命中直接查方法列表
    }

    // 2. 缓存未命中,查方法列表
    method = class_getMethod(cls, selector);

    if (method) {
        // 3. 填充缓存
        if (cls->cache) {
            cache_fill(cls->cache, method);
        }

        // 4. 调用 IMP
        printf("[MSGSEND] Calling IMP=%p\n", (void *)method->imp);

        // 调用函数(简化版,不处理参数)
        // 真实实现需要根据 types 解码参数并正确传递
        typedef void (*IMP)(void);
        IMP imp = (IMP)method->imp;
        imp();

        return NULL;  // 简化版
    }

    // 5. 方法未找到:消息转发
    printf("[MSGSEND] Method NOT FOUND for selector: %s\n", selector);
    return NULL;
}

// ================= 类注册 =================

static Class alloc_class(const char *name, Class superclass) {
    Class cls = calloc(1, sizeof(struct objc_class));
    cls->name = name;
    cls->superclass = superclass;
    cls->cache = cache_create(16);  // 初始容量 16
    return cls;
}

Class objc_registerClass(const char *name, Class superclass) {
    Class cls = alloc_class(name, superclass);

    // 创建元类
    Class meta = alloc_class(name, superclass ? superclass->isa : NULL);
    cls->isa = meta;
    meta->isa = meta;  // 元类的 isa 指向自己

    printf("[CLASS REG] Registered: %s (super=%s)\n",
           name, superclass ? superclass->name : "(nil)");

    return cls;
}

void objc_addMethod(Class cls, const char *name, const char *types, void (*imp)(void)) {
    if (!cls || !name || !imp) return;

    // 重新分配方法列表
    uint32_t old_count = cls->methods ? cls->methods->count : 0;
    uint32_t new_count = old_count + 1;

    MethodList new_list = realloc(cls->methods,
        sizeof(struct method_list_t) + new_count * sizeof(struct method_t));

    new_list->methods[old_count].name = name;
    new_list->methods[old_count].types = types;
    new_list->methods[old_count].imp = imp;

    cls->methods = new_list;

    printf("[METHOD ADD] class=%s, method=%s, imp=%p\n",
           cls->name, name, (void *)imp);
}

3.2 使用示例

c 复制代码
// main.c

#include "message.h"
#include <stdio.h>

// 定义方法实现
void greet_impl(void) {
    printf("  → Hello from Greeter!\n");
}

void farewell_impl(void) {
    printf("  → Goodbye from Greeter!\n");
}

void describe_impl(void) {
    printf("  → I'm a Greeter instance\n");
}

// 动态添加方法(模拟 @dynamic)
void addGreetMethod(Class cls) {
    objc_addMethod(cls, "greet", "v@:", greet_impl);
}

void addFarewellMethod(Class cls) {
    objc_addMethod(cls, "farewell", "v@:", farewell_impl);
}

int main(void) {
    printf("=== 手写 objc_msgSend 消息分发系统 ===\n\n");

    // 1. 注册类
    Class greeterClass = objc_registerClass("Greeter", NULL);
    objc_addMethod(greeterClass, "describe", "v@:", describe_impl);

    // 2. 创建对象
    id obj = calloc(1, sizeof(struct objc_object));
    obj->isa = greeterClass;

    // 3. 调用已存在的方法
    printf("1. 调用 describe 方法(已存在):\n");
    objc_msgSend(obj, "describe");

    // 4. 动态添加方法并调用
    printf("\n2. 动态添加 greet 方法并调用:\n");
    addGreetMethod(greeterClass);
    objc_msgSend(obj, "greet");

    // 5. 第二次调用(测试缓存)
    printf("\n3. 第二次调用 greet(测试缓存命中):\n");
    objc_msgSend(obj, "greet");

    // 6. 调用不存在的方法
    printf("\n4. 调用不存在的方法 unknownMethod:\n");
    objc_msgSend(obj, "unknownMethod");

    // 7. nil receiver
    printf("\n5. nil receiver:\n");
    objc_msgSend(NULL, "describe");

    printf("\n=== 演示结束 ===\n");

    free(obj);
    return 0;
}

运行输出

ini 复制代码
=== 手写 objc_msgSend 消息分发系统 ===

[CLASS REG] Registered: Greeter (super=(nil))
[METHOD ADD] class=Greeter, method=describe, imp=0x102a3c123

1. 调用 describe 方法(已存在):
[MSGSEND] obj=0x7f8b2c001000, isa=Greeter, selector=describe
[METHOD LOOKUP] Found 'describe' in class 'Greeter'
[CACHE FILL] selector=describe -> imp=0x102a3c123 (index=3)
  → I'm a Greeter instance

2. 动态添加 greet 方法并调用:
[METHOD ADD] class=Greeter, method=greet, imp=0x102a3c456
[MSGSEND] obj=0x7f8b2c001000, isa=Greeter, selector=greet
[METHOD LOOKUP] Found 'greet' in class 'Greeter'
[CACHE FILL] selector=greet -> imp=0x102a3c456 (index=7)
  → Hello from Greeter!

3. 第二次调用 greet(测试缓存命中):
[MSGSEND] obj=0x7f8b2c001000, isa=Greeter, selector=greet
[CACHE HIT] selector=greet -> imp=0x102a3c456
  → Hello from Greeter!

4. 调用不存在的方法 unknownMethod:
[MSGSEND] obj=0x7f8b2c001000, isa=Greeter, selector=unknownMethod
[METHOD LOOKUP] NOT FOUND: 'unknownMethod'
[METHOD NOT FOUND] selector=unknownMethod

5. nil receiver:
[MSGSEND] obj=0x0, selector=describe -> NIL

=== 演示结束 ===

四、Swift 的方法分发策略详解

4.1 Swift 的四种分发策略

swift 复制代码
// Swift 方法分发表

class Animal {
    // 1. 直接分发(final)
    final func sleep() {
        print("Animal sleeps")  // 直接调用,零开销
    }

    // 2. 虚表分发(默认 class 方法)
    func eat() {
        print("Animal eats")    // 通过虚表间接调用
    }

    // 3. 消息分发(dynamic / @objc)
    dynamic func speak() {
        print("Animal speaks")  // objc_msgSend
    }

    @objc func makeNoise() {
        print("Animal makes noise")  // objc_msgSend
    }
}

// 4. 协议方法:虚表分发(泛型)或消息分发(existential)
protocol Pet {
    func play()  // 取决于调用方式
}

4.2 Swift 6 的变化

Swift 6 对方法分发的规则更加严格:

swift 复制代码
// Swift 6: dynamic 隐含 @objc
class Foo {
    dynamic func method() { }  // Swift 6 中 dynamic 意味着 @objc
}

// Swift 6: @objc 推断规则更严格
class Bar {
    @objc func method() { }  // 必须显式标记
}

// Swift 6: 禁止某些分发方式混用
class Baz {
    // 错误:final 和 dynamic 互斥
    // final dynamic func method() { }  // Error!
}

4.3 方法分发的性能对比

swift 复制代码
import Foundation

class TestClass {
    final func finalMethod() { }      // 最快:直接调用
    func virtualMethod() { }          // 中等:虚表查找
    @objc dynamic func msgMethod() { } // 最慢:消息传递
}

// 性能测试结果(相对值,MacBook Pro M3)
//
// final method:      1x    (基准)
// virtual method:     1.2x  (虚表一次查找)
// objc_msgSend:       2-5x  (缓存未命中时)
// objc_msgSend cached: 1.5x  (缓存命中时)

五、Method Swizzling 的原理

理解了 objc_msgSend 后,Method Swizzling 的原理就很清晰了:

c 复制代码
// Method Swizzling = 交换两个方法的 IMP

void method_exchangeImplementations(Method m1, Method m2) {
    // 交换两个方法的实现指针
    IMP imp1 = m1->imp;
    IMP imp2 = m2->imp;

    m1->imp = imp2;  // method1 现在指向 method2 的实现
    m2->imp = imp1;  // method2 现在指向 method1 的实现
}

交换之后,每次调用 selector1 实际上会执行 selector2 的实现,反之亦然。

Swift 中的等价操作

swift 复制代码
// Swift 中使用 method_exchangeImplementations
import ObjectiveC

extension UIViewController {
    static func swizzleViewWillAppear() {
        let original = #selector(UIViewController.viewWillAppear(_:))
        let swizzled = #selector(UIViewController.swizzled_viewWillAppear(_:))

        guard let originalMethod = class_getInstanceMethod(UIViewController.self, original),
              let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzled)
        else { return }

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }

    @objc func swizzled_viewWillAppear(_ animated: Bool) {
        print("Swizzled: viewWillAppear")
        // 在这里可以添加日志、埋点等通用逻辑
        // 调用原方法(注意:现在已经交换了!)
        swizzled_viewWillAppear(animated)
    }
}

六、消息转发机制

当 objc_msgSend 在缓存和方法列表中都找不到方法时,会触发消息转发流程:

scss 复制代码
objc_msgSend
    │
    ├─→ [1. 缓存查找] ──→ HIT ──→ 调用 IMP
    │
    ├─→ [2. 方法列表查找] ──→ FOUND ──→ 填充缓存 ──→ 调用 IMP
    │
    └─→ [3. 消息转发](三次机会)
            │
            ├─→ forwardInvocation  (可以做任意处理)
            │
            ├─→ methodSignatureForSelector(返回方法签名)
            │
            └─→ doesNotRecognizeSelector(最终报错)

Swift 的消息转发比 Objective-C 弱,因为 Swift 不鼓励使用运行时特性。


总结

今天我们深入了 Swift 运行时最核心的部分:

ini 复制代码
objc_msgSend 执行流程
│
├── 1. nil 检查
│   └── obj == nil → 返回 nil(大多数情况)
│
├── 2. 缓存查找
│   ├── hash = selector & mask
│   ├── 线性探测 open-addressing
│   └── 命中 → 直接跳转到 IMP
│
├── 3. 方法列表查找(缓存未命中)
│   ├── 从当前类开始
│   ├── 沿继承链向上遍历
│   └── 找到 → 填充缓存 → 调用 IMP
│
├── 4. 消息转发(方法未找到)
│   ├── forwardInvocation
│   ├── methodSignatureForSelector
│   └── doesNotRecognizeSelector
│
└── 5. 调用 IMP
    └── CPU 跳转到实现地址(虚表或直接)

关键理解

  • 方法缓存是性能的核心:80%+ 的方法调用在缓存层命中
  • Swift 的分发策略比 Objective-C 丰富,编译期能做更多优化
  • dynamic@objc 才会触发消息传递
  • Method Swizzling 是通过直接交换 IMP 实现的

下篇预告

下一篇文章我们将探索 Swift 运行时的另一个核心主题:类结构与元类(MetaClass)体系 ------Class 的内存布局、class_rw_t vs class_ro_t 的区别,以及 Swift 类型如何在 Objective-C 运行时中注册。


往期回顾


如果这篇「手写」系列有价值,欢迎点赞并在评论区告诉我你想手写什么框架。

相关推荐
2601_956002812 小时前
AdGuardPro_TS.ipa2026最新版ipa 下载后浏览器无广告 官方正版2026最新版pc免费下载(看到请立即转存 资源随时失效)ios必下
macos·ios·cocoa·ipa
子兮曰2 小时前
AI Coding 为什么全选了 TUI?从 Claude Code 到 Codex CLI,终端架构的底层逻辑
前端·后端·ai编程
JiaHao汤2 小时前
深入理解 Claude Code 规则体系.md
ai编程
AI原来如此2 小时前
[特殊字符]2026AI Agent入门学习路径
学习·ai·大模型·ai编程
Daniel_Coder2 小时前
iOS Widget 开发-12:Widget 深度链接与导航
ios·swiftui·swift·widget·intents
小坏讲微服务2 小时前
SpringBoot整合SpringAI配置多平台API密钥
java·人工智能·spring boot·后端·flask·ai编程·claude code
暗不需求2 小时前
深入浅出 LangChain Memory:从无状态到有记忆的智能对话
面试·langchain·ai编程
han_3 小时前
如何寻找、安装和管理 AI Skill?
人工智能·ai编程·claude
无敌糖果3 小时前
Azure OpenAI配置Codex对接模型apikey
openai·azure·codex