Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”

引子

Swift 一直有个很骄傲的设定:值类型很安全,引用类型很灵活

安全是好事,但写高性能代码时,有时候你会遇到一种尴尬:

我只是想"临时抓住某个值的一小块",反复读它、改它,别每次都重新找一遍。 但 Swift:你可以传 inout 参数啊。 你:我不想为了这点事专门写个 helper 函数! Swift 6.4:行,给你 RefMutableRef

WWDC26 的 What's new in Swift 里,Apple 把 Ref / MutableRef 放在 Swift 标准库新类型部分一起介绍,目标是支持更安全、更高性能的 ownership 编程模式。Swift Evolution 的 SE-0519 也已经标记为 Implemented,Swift 6.4。(Apple Developer1)

这篇文章就围绕一个实际的例子,讲清楚它们到底解决什么问题、为什么以前的写法别扭,以及 MutableRef 为什么像是 Swift 给性能党递上的一把"合法撬棍"。


一、先说人话:RefMutableRef 是什么?

一句话:

Ref 是对某个值的只读借用引用MutableRef 是对某个值的独占可变引用

它们不是 class,不是 UnsafePointer,也不是 C++ 那种可以到处乱飞的引用。Swift Evolution 对它们的定位很明确:这是两个标准库里的泛型类型,用来表示对另一个值的安全引用;Ref 表示 shared borrow,只能读;MutableRef 表示 exclusive mutable access,可以改。

大概可以这么理解:

swift 复制代码
let ref = Ref(someValue)
// ref.value 只能读

var mutableRef = MutableRef(&someValue)
// mutableRef.value 可以读,也可以改

看起来像指针,但它不是"裸指针"。

看起来像引用,但它不等于 class

看起来像 inout,但它终于可以被放进一个局部变量里了。

这就是关键。


二、Swift 以前的问题:inout 很强,但太"临时工"

Swift 早就有 inout

swift 复制代码
func increment(_ value: inout Int) {
    value += 1
}

var count = 0
increment(&count)

这很好。increment 在调用期间拿到 count 的独占可变访问权,函数结束后归还。

问题是:以前这种访问主要只能存在于函数调用边界里

也就是说,你可以这样:

swift 复制代码
someFunction(&dictionary[key, default: 0])

但你不容易这样:

swift 复制代码
var entry = &dictionary[key, default: 0] // 以前没有这种局部引用绑定

SE-0519 的动机里说得很直白:Swift 已经能把临时访问作为函数参数传进去,比如 inout 参数拿到临时独占访问,borrowing 参数拿到临时共享访问;但开发者也需要把这种访问形成局部变量、类型成员、泛型容器元素等。

过去可以用 class box 或 UnsafePointer 绕,但前者有分配、引用计数和动态 exclusivity 检查开销,后者则不安全。

翻译成人话:

Swift 以前不是不能借,只是只能"当场借、当场还"。 你想把"借来的访问权"装进变量里慢慢用?以前就难受了。


三、性能坑:字典不是自动售货机,别每次都投币

来看 WWDC26 中的一个例子:

swift 复制代码
func updateCount<Key: Hashable>(
    for key: Key,
    from sets: [Set<Key>],
    in counts: inout [Key: Int]
) {
    for set in sets {
        if set.contains(key) {
            counts[key, default: 0] += 1
        }
    }
}

这段代码很自然,99% 的业务代码这么写都没问题。

但如果 sets 很大,循环很多,而 key 固定,那么这句:

swift 复制代码
counts[key, default: 0] += 1

每次循环都要重新访问字典里的同一个槽位。

字典访问不是魔法。它通常涉及哈希、查找 bucket、处理冲突、定位元素等步骤。即使 Swift 标准库内部已经做了很多优化,但你在循环里反复访问同一个 key,本质上还是有重复工作。

就像你去酒店住十天,前台每天都让你重新登记身份证、刷脸、填表、拿房卡。不是不能住,就是有点像在玩自己。


四、以前的解决方法:性能救了,可读性瘸了

按照以前的 Old 方法,我们可以写一个 helper 局部函数来解决:

swift 复制代码
func updateCount<Key: Hashable>(
    for key: Key,
    from sets: [Set<Key>],
    in counts: inout [Key: Int]
) {
    func updateCountImpl(count: inout Int) {
        for set in sets {
            if set.contains(key) {
                count += 1
            }
        }
    }

    updateCountImpl(count: &counts[key, default: 0])
}

这个技巧的思路是:

  1. 先通过 &counts[key, default: 0] 对字典中的某个值形成一次 inout 访问;
  2. 把这个访问交给内部函数 updateCountImpl
  3. 在内部函数里反复修改 count
  4. 这样就不用在循环里每次重新走字典 subscript。

从性能角度看,这很聪明。

从可读性角度看,这有点像为了开门,像先造了一间门卫室。

问题不是这段代码错,而是它把"我想临时抓住这个字典槽位"这件事,包装成了一个内部函数调用。读代码的人第一眼会想:

为什么这里突然冒出一个 updateCountImpl? 是业务逻辑拆分? 是递归铺垫? 是作者昨晚咖啡喝多了?

其实都不是。它只是为了延长 inout 访问的作用范围。

这就是 MutableRef 要解决的痛点!


五、Swift 6.4 的新写法:把"访问权"装进变量里

现在,我们来看看最新的 MutableRef 如何大显身手:

swift 复制代码
func updateCount<Key: Hashable>(
    for key: Key,
    from sets: [Set<Key>],
    in counts: inout [Key: Int]
) {
    var countRef = MutableRef(&counts[key, default: 0])

    for set in sets {
        if set.contains(key) {
            countRef.value += 1
        }
    }
}

这就舒服多了。

核心语句是:

swift 复制代码
var countRef = MutableRef(&counts[key, default: 0])

它的含义是:

counts[key, default: 0] 这个可变位置形成一个 MutableRef,之后通过 countRef.value 访问它。

SE-0519 里也给了非常相似的例子:先从字典中 project 出某个 key 对应的 entry,形成一个 MutableRef,然后在循环中反复修改 entry.value,避免重复查找哈希表。

代码的表达终于贴近意图了:

swift 复制代码
var countRef = MutableRef(&counts[key, default: 0])

它不像 helper 函数那样拐弯,也不像 UnsafeMutablePointer 那样吓人。

它表达的是:

我现在要独占访问这个位置。 在 countRef 活着的这段时间里,我会通过它改值。 Swift,请你继续帮我看住安全边界。


六、MutableRef 的安全边界:你独占,就别同时乱碰原对象

MutableRef 最重要的不是"能改",而是"独占地改"。

SE-0519 明确说明:RefMutableRef 都是 non-Escapable 类型,形成后会携带对目标值的生命周期依赖。Ref 存活时,目标值处于借用状态;MutableRef 存活时,目标值处于独占访问状态。也就是说,在 MutableRef 活着的时候,你不能直接再去动原始容器。

例如概念上类似:

swift 复制代码
var totals = [17, 38]

do {
    var bananas = MutableRef(&totals[1])

    bananas.value += 2

    // 这里不能再直接访问/修改 totals[1]
    // 因为 bananas 正在独占访问它

    bananas.value += 2
}

print(totals)

这不是 Swift 小气,而是 Swift 在守规矩。

因为如果你已经把 totals[1] 的可变访问权交给 bananas,同时又允许你绕过 bananas 去改 totals,那就等于酒店已经把房卡给你了,前台又偷偷把同一间房开给别人。出事只是时间问题。

所以 MutableRef 的核心不是"让我像 C 指针一样自由",而是:

让我像 C 指针一样高效一点,但不要像 C 指针一样半夜把内存炸了。


七、Ref 是什么场景?只读版的小窗口

Ref 可以理解为 MutableRef 的只读兄弟:

swift 复制代码
let nameRef = Ref(user.name)
print(nameRef.value)

它用于 shared borrow,也就是共享借用。你可以读,但不能修改,更不能 consume 目标值。SE-0519 给出的接口草图里,RefCopyable & ~Escapable,而 MutableRef~Copyable & ~EscapableRef.value 提供 borrowMutableRef.value 同时提供 borrowmutate

为什么 Ref 可以 Copyable,而 MutableRef 不行?

因为多个只读引用通常没问题,大家一起看菜单,不会把菜看没。

但可变引用不能随便复制。两个 MutableRef 同时声称"我独占这个值",那就像两个孙悟空都说自己是真的,编译器当然要把其中一个按住。


八、它和 Span / MutableSpan 是什么关系?

SE-0519 里有一个很好的类比:可以把 Ref / MutableRef 看成 Span / MutableSpan 的单元素版本。Span / MutableSpan 面向一段连续元素,而 Ref / MutableRef 面向单个值。

所以你可以这样记:

类型 访问对象 权限
Ref<T> 一个 T 只读
MutableRef<T> 一个 T 可变、独占
Span<T> 一段 T 只读
MutableSpan<T> 一段 T 可变、独占

这个设计背后是 Swift ownership 系统继续往前走:不只是"值类型安全",还要让安全和性能同时站住。

以前很多人一谈性能就忍不住掏 UnsafePointer,像武侠片里动不动就拔刀。现在 Swift 的方向是:能不能给你一把有刀鞘、有护手、不会误伤自己的刀?

Ref / MutableRef 就是这条路上的一块拼图。


九、为什么它能提升性能?

以字典例子来说,收益来自两点。

第一,避免重复定位同一个存储位置

swift 复制代码
counts[key, default: 0] += 1

放在循环里,每次都要访问 subscript。使用 MutableRef 后:

swift 复制代码
var countRef = MutableRef(&counts[key, default: 0])

for set in sets {
    if set.contains(key) {
        countRef.value += 1
    }
}

字典 entry 的访问被提前形成,循环里只通过 countRef.value 修改。

第二,减少 get-update-set 形式可能带来的额外开销

Swift 近年来一直在增强 accessor 模型。和 Ref / MutableRef 相关的还有 borrow / mutate accessor、yielding accessor 等机制。SE-0474 解释了 yielding mutate 的目标:允许在不先复制值的情况下原地修改;它不像传统 get/set 那样先取出一个值、改完再塞回去,而是把可变访问"借给"调用方。

MutableRef 站在这套机制上,把"可变访问"变成一个可以命名、可以传递、可以组合的值。

一句话:

以前你每次都问:"这个 key 对应的值在哪里?" 现在你先问一次,然后拿着房卡直接进门。


十、它不是什么?

这个很重要,否则容易把新特性用歪。

1. 它不是 class

class 是引用语义。多个地方拿到同一个对象引用,都可以长期持有它。

Ref / MutableRef 是临时访问权。它们是 non-Escapable,生命周期受目标值约束,不能随便逃逸出去长期保存。SE-0519 明确说它们形成后携带 lifetime dependency,只能在目标值仍能保持相应访问状态时使用。

所以别把它当成"轻量 class"。

它更像:

借你一张临时通行证,用完必须还。

2. 它不是 UnsafePointer

UnsafePointer / UnsafeMutablePointer 的问题是:你得自己保证地址有效、生命周期正确、没有并发乱改。

Ref / MutableRef 的目标是把这种"指向某个值"的能力纳入 Swift 的类型系统和生命周期检查里。SE-0519 的动机部分也直接指出,UnsafePointer 不安全,并且和 Swift 高层语义配合起来很别扭。

所以它不是让你裸奔,而是让你穿着护甲跑得更快。

3. 它不是普通业务代码的必需品

绝大多数 SwiftUI App、普通网络请求、表单页面、列表页面,不需要上来就写 MutableRef

你不需要为了给按钮计数写这个:

swift 复制代码
var ref = MutableRef(&count)
ref.value += 1

这就像买菜开装甲车。能开,但邻居会报警。

它真正适合的是:

  • 容器内部实现;
  • 高频循环里的局部元素反复访问;
  • 字典、数组、嵌套结构里的局部可变窗口;
  • 非拷贝类型、ownership-heavy API;
  • 对性能和内存行为非常敏感的底层代码。

十一、一个更贴近业务的例子:统计用户命中次数

假设你有一批用户标签集合,要统计某个标签出现了多少次:

swift 复制代码
func countTag(
    _ tag: String,
    in userTags: [Set<String>],
    result: inout [String: Int]
) {
    for tags in userTags {
        if tags.contains(tag) {
            result[tag, default: 0] += 1
        }
    }
}

普通写法没问题,但如果这是热点路径,可以改成:

swift 复制代码
func countTag(
    _ tag: String,
    in userTags: [Set<String>],
    result: inout [String: Int]
) {
    var count = MutableRef(&result[tag, default: 0])

    for tags in userTags {
        if tags.contains(tag) {
            count.value += 1
        }
    }
}

可读性反而更直接:

swift 复制代码
var count = MutableRef(&result[tag, default: 0])

这句的潜台词是:

接下来我要反复改的就是这个计数值。 别让我每次都钻进字典里重新找它。

这就是好 API 的味道:它让代码表达意图,而不是表达绕路方案。


十二、MutableRef 和 helper 函数写法的本质区别

以前:

swift 复制代码
func updateCountImpl(count: inout Int) {
    ...
}

updateCountImpl(count: &counts[key, default: 0])

这其实是"借函数调用之名,行延长访问之实"。

现在:

swift 复制代码
var countRef = MutableRef(&counts[key, default: 0])

这是直接把访问抽象成值。

区别很大。

helper 函数写法的问题是:控制流被迫改变。你明明只是想声明一个局部变量,却要创建一个函数,把后续逻辑塞进去。

MutableRef 写法的问题少得多:访问权就是访问权,变量就是变量。代码结构没有被性能技巧绑架。

用一句不太严肃但很准确的话说:

helper 函数是"偷渡"; MutableRef 是"办证入境"。


十三、它背后的 Swift 大方向:值语义不是慢的借口

Swift 早期给很多人的印象是:

  • 安全;
  • 现代;
  • 适合 App;
  • 但底层性能控制不如 C/C++ 直接。

这几年 Swift 在补的,正是这个短板:ownership、noncopyable、borrow、consume、Span、InlineArray、UniqueArray、UniqueBox、Ref、MutableRef......这些特性看起来都偏底层,但它们共同解决一个问题:

能不能让开发者写出接近系统级性能的代码,同时仍然享受 Swift 的类型安全?

WWDC26 的 Swift session 也把 ownership system、noncopyable types、borrow/mutate accessors 和新的标准库类型放在性能调优这一条线上介绍。

所以 Ref / MutableRef 不是孤立的小玩具。它们是 Swift 继续向"安全系统语言"进化的一部分。

以前 Swift 对开发者说:

你放心写,我帮你安全。

现在 Swift 对更高级的开发者说:

你想抠性能?可以。但别把安全扔了,我给你正规工具。

这句话其实挺狠。

因为它等于告诉你:以后再随便掏 UnsafePointer,借口少了。


十四、什么时候该用?什么时候别碰?

可以用的情况:

swift 复制代码
var ref = MutableRef(&dictionary[key, default: 0])

适合这种模式:

  1. 你要访问的是某个容器或结构里的局部位置;
  2. 这个位置会被反复读写;
  3. 每次重新访问这个位置有明显成本;
  4. 代码处于性能热点路径;
  5. 你能清楚解释为什么普通写法不够好。

不建议用的情况:

swift 复制代码
var ref = MutableRef(&someSimpleLocalInt)
ref.value += 1

如果只是普通局部变量:

swift 复制代码
someSimpleLocalInt += 1

就完事了。

不要为了"显得懂 Swift 6.4"而到处写 MutableRef。技术文章里可以炫,生产代码里别乱炫。

代码不是朋友圈,没人给你点赞,只会在三个月后骂你。

你可以问自己一句话:

我是不是在循环里反复访问同一个"昂贵的子位置"?

比如:

swift 复制代码
dictionary[key, default: 0]
array[index].some.deep.property
matrix[row][column]
storage[id]!.metadata.count

如果答案是"是",并且这段代码真的在热点路径上,那么 MutableRef 值得考虑。

如果答案是"不确定",那就先别用。

Swift 的性能优化,永远应该先靠 Instruments、benchmark、真实数据说话。不要靠"我感觉这里很高级"。


十五、总结:MutableRef 让 Swift 少了一点绕,多了一点狠

RefMutableRef 最漂亮的地方,不是语法多炫,而是它们补上了 Swift 访问模型里一个长期缺口:

以前 inout 很强,但它主要活在函数参数里。 现在可变访问可以被抽象成一个安全的、受生命周期约束的值。

对于普通 App 开发者,它可能不是天天用的工具。

✅最新的 Swift 6.4 Toolchain 下载地址: www.swift.org/install/mac...

但对于写容器、写算法、写底层库、优化热点路径的人来说,它很重要。

因为它把以前这种晦涩写法:

swift 复制代码
func impl(value: inout Int) {
    ...
}

impl(value: &dictionary[key, default: 0])

变成了更直接的:

swift 复制代码
var valueRef = MutableRef(&dictionary[key, default: 0])

这不是小修小补。

这是 Swift 在说:

我知道你想要性能。 我也知道你不想为了性能把代码写成密宗符咒。 来,这次给你一把正规钥匙。

MutableRef 的价值就在这里: 它让 Swift 代码在需要的时候,可以更接近底层; 但又不至于一脚踩进 unsafe 的沼泽。

这东西不会让每个 App 都变快,但它会让真正懂性能的人少写很多"看起来像法术"的代码。对于 Swift 来说,这是一次很漂亮的进化。

感谢宝子的观赏,这次是否受益良多呢?再会啦!8-)

相关推荐
黑科技iOS上架2 小时前
没有mac电脑如何借助windows系统上传ipa到App Store
经验分享·ios
Layer3 小时前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_12 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
大熊猫侯佩17 小时前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者818 小时前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
大熊猫侯佩1 天前
WWDC26 最被忽视的王炸:告别“伪并发”陷阱,Swift 6.4 的 async defer
ios·swift·编程语言
h-189-53-6712071 天前
苹果开发者账号防关联3.2f隔离环境传包提审iOS开发上架的高效隔离方案:iOSUploader工具实用解析
ios·ios上架·ios审核·苹果审核·苹果开发者账号·苹果开发者封号
Legendary_0081 天前
LDR6020P:iPad 一体式皮套键盘 OTG 应用的核心引擎
ios·计算机外设·ipad
2601_951645782 天前
Linux 编程语言全解析:C、C++、Python、Go、Rust 谁更强?
linux·python·go·c·编程语言