Swift 底层核心知识(值类型,引用类型,copy-on-write原理)

4 阅读4分钟

🧠 Swift 底层核心知识:终极复习备忘单

一、值类型的多态(Existential Container / VWT / PWT)

核心难题:结构体大小不一,怎么放进要求元素等大的数组?怎么调协议方法?

· Existential Container(存在容器/盒子):固定大小(3个字长),统一包装不同类型。 · 暗格1(值缓冲区):小数据直接存;大数据放不下就在堆上开空间,这里只存指针。 · 暗格2(指向 VWT):管理“生死”(复制、销毁、内存大小)。 · 暗格3(指向 PWT):管理“行为”(协议方法的实现地址)。 · VWT(值见证表):按类型共享一份(tiger1 和 tiger2 共用同一张)。 · 职责:allocate、copy、destroy、deallocate。 · PWT(协议见证表):按类型共享一份。 · 职责:记录该类型对协议方法的具体实现地址,实现动态派发。


二、三大派发机制横向对比

特性 Swift 值类型 (Struct) Swift 类 (Class) Objective-C 派发表 PWT(协议表) + VWT(生命周表) V-Table(虚表) Runtime 方法列表 + Cache 查表方式 通过 Existential Container 指针找到 PWT,间接跳转 通过 isa 找到 V-Table,固定偏移量直接跳转(最快) objc_msgSend 动态查找、缓存、转发(最慢) 表是否共享 是(按类型共享) 是(按类共享) 是(按类共享) 灵活性 低(编译期固定) 中(可继承重写,但不可运行时替换) 极高(Method Swizzling、动态添加、消息转发) 内存管理 VWT 管栈/堆上的值(无引用计数) ARC(引用计数) ARC(引用计数)


三、@objc dynamic 的奥秘(打通 Swift 和 OC)

· @objc:仅暴露给 OC 运行时(开门),不改变派发方式(依然走 V-Table)。 · dynamic:强制走 OC 消息派发(objc_msgSend),放弃 V-Table。 · 组合效果:开启运行时消息转发机制,实现方法拦截。

拦截与崩溃捕捉

· 不加 @objc dynamic:走 V-Table(铁轨直达),没有“问路”环节,无法拦截未实现方法。 · 加 @objc dynamic:走消息派发,可重写 OC 的 forwardingTarget(for:) 实现优雅转发,避免崩溃。


四、值类型的大对象(堆分配)生命周期

场景:结构体太大,Existential Container 的 3 个字长放不下,在堆上开辟空间。

· 有引用计数吗? 绝对没有!(除非内部包裹了 Class) · 怎么管理? 纯静态作用域管理(类似 C++ 的 RAII)。 · 创建:VWT 调用 malloc 分配堆内存。 · 复制:VWT 调用 copy,直接深拷贝一块全新的堆内存(两份独立)。 · 销毁:盒子出栈时,编译器自动插入 VWT 的 destroy 和 deallocate,直接 free 物理回收。 · 结论:因为只有唯一拥有者(不存在共享),所以无需引用计数,效率比 ARC 更高。


五、写时复制(Copy-on-Write,CoW)的原理

目的:让大值类型(如 Array、String)在赋值时不要立刻深拷贝,节省内存。

底层结构:

struct Array {
    internal var _storage: __ArrayStorage // 这是一个 Class,有引用计数!
}

执行流程:

  1. 赋值(let b = a):只拷贝指针(8字节),引用计数 +1,不拷贝堆内存。
  2. 修改前检查(b.append()):调用 isKnownUniquelyReferenced(&_storage)。
  3. 判断: · 引用计数 > 1(说明在共享):触发真正深拷贝,创建新堆内存,修改新副本。 · 引用计数 == 1(独享):原地修改,不拷贝。

· 本质:“披着值类型外衣的引用类型,修改时才脱衣服”。


六、⚠️ 致命陷阱:把 Class 实例塞进 Existential Container

场景:class A 的实例被放进 Existential Container,然后复制这个盒子。

· 内存行为:只复制了指向 class A 的指针(8字节),堆上的实例仅有一份,引用计数 +1。 · 支持 CoW 吗? 绝对不支持! 系统不会因为复制盒子就帮你深拷贝类实例。 · 后果:修改 盒子2 里的类属性,盒子1 也跟着变。这是值类型盒子里装了个引用炸弹。 · 如何修复? 必须手动封装:

struct MyValue {
    private var storage: AStorage // 类
    mutating func change() {
        if !isKnownUniquelyReferenced(&storage) {
            storage = AStorage(storage) // 手动深拷贝
        }
        // 修改...
    }
}

🚀 一句话极速回忆(必记口诀)

  1. PWT/VWT = 按类型共享的说明书(管行为和生死),tiger1 和 tiger2 用同一本。
  2. V-Table vs Runtime = 高铁直达(Swift类) vs 总机转接(OC)。@objc dynamic 是切换开关。
  3. 大值类型堆内存 = 没引用计数,VWT 直接 malloc/free,复制即深拷贝。
  4. CoW = 数组赋值只复制指针,只有修改时且发现计数 >1 才真复制。
  5. 致命陷阱 = 盒子里装类 = 只复制指针,不改则共享,改了则同变,不支持自动 CoW。 。