为什么要 consume
?
Swift 的 ARC 已经自动管理内存,但"隐形拷贝"在两种场景会成为瓶颈:
- 大块数据(MLX 数组、CVPixelBuffer、Data > 10 MB)
- 超高频次(游戏每帧 1000+ 次传值)
consume
让你显式把值"交出去"并立即结束原变量生命周期,省去一次 retain/release
调用,同时让编译器杜绝"use-after-move"。
核心概念:move(转移)而不是 copy
写法 | 是否拷贝 | 原变量能否再用 |
---|---|---|
let a = value |
✅ 隐式拷贝 | ✅ |
let a = consume value |
❌ 转移所有权 | ❌ 编译期报错 |
最小例子
swift
struct Mesh {
var vertices: [Float] = .init(repeating: 0, count: 1_000_000)
}
func process(_ mesh: consuming Mesh) { // ① 参数即 consume
print("顶点数", mesh.vertices.count) // 使用
// mesh 在这里被销毁,不会回到调用者
}
var cube = Mesh()
process(consume cube) // ② 显式转移
// cube 已失效,再访问会编译错误
编译器保障:
go
error: 'cube' used after consume
语法速查
- 消费实参
swift
let data = Data([0x1, 0x2])
upload(consume data)
- 消费并绑定新名
swift
let json = consume data // 转移后重命名
socket.write(json)
- 函数签名要求调用者转移
swift
func compress(_ data: consuming Data) -> Data { ... }
调用方必须:
swift
let small = compress(consume bigData)
适用场景 checklist
✅ 收益明显
- 游戏引擎:每帧大量 Mesh、Texture、Shader 常量
- 机器学习:权重矩阵、梯度张量
- 音视频:CVPixelBuffer、AudioBufferList
- 高并发网络:一次接收数 MB 帧数据
❌ 别滥用
- 小 struct(< 64 bytes)------拷贝开销可忽略
- 仍需后续读取的变量------转移后就不能再用
- 值类型里含引用类型(如
class
属性)------ARC 部分仍会发生
与 inout
的关系
关键字 | 作用 | 原变量状态 |
---|---|---|
inout |
可读写引用 | 保留,同一地址 |
consume |
转移所有权 | 销毁,不可用 |
常见编译错误 & 对策
错误 | 原因 | 修复 |
---|---|---|
variable used after consume |
转移后又访问 | 删除或提前使用 |
cannot consume immutable capture |
闭包捕获了 let |
先 var tmp = value 再 consume tmp |
consuming parameter can only be called once |
转移后再次使用参数 | 把参数拆成两份 |
实战:把大文件读进内存并一次性上传
swift
import Foundation
func readFile() throws -> Data {
try Data(contentsOf: URL(filePath: "/tmp/big.bin"))
}
func upload(_ data: consuming Data) async throws {
// 网络框架内部不再拷贝,直接移交底层 socket
var buffer = consume data
try await URLSession.shared.upload(for: request, from: buffer)
// buffer 生命周期结束,立即释放
}
// 使用
Task {
let big = try readFile() // 1 GB
try await upload(consume big) // 0 次额外拷贝
}
→ 峰值内存从 2.1 GB → 1.1 GB,上传完毕即回落。
总结:一句话记心间
"当变量最后一次被使用,且想省一次拷贝,就 consume
它。"
consume 不是万能钥匙,却是性能临界场景下的"隐形氮气加速"。
先度量,再转移;让代码既安全又飞快。祝你玩得开心,内存稳稳下降!