面向 Swift 开发者的 WebAssembly 入门
WebAssembly (Wasm) 是一种在隔离虚拟机中运行的低级二进制指令。对于 iOS/macOS 上的 Swift 开发者来说,集成 Wasm 开辟了新的可能性:移植其他语言的代码、为第三方库提供沙箱环境,以及无需原生代码即可执行高性能计算。本文将探讨在 Swift 中使用 WebAssembly 的高级技术:从编译到直接内存操作和函数调用。
与基础教程不同,我们将聚焦于实际任务:复杂数据类型的传递、手动内存管理以及性能优化。假设您已熟悉 Swift 基础知识和 WebAssembly 的基本概念。
为 Swift 编译 WebAssembly 代码
工具与配置
要编译第三方代码(C/C++/Rust)为 Wasm,请使用 Emscripten 或 wasm-pack(适用于 Rust)。对于纯 Swift,有 SwiftWasm 项目,但其功能有限。大多数情况下,您将集成现成的模块。
将简单 C 函数编译为 Wasm 的示例:
// math.cint add(int a, int b) { return a + b;}命令:
emcc math.c -o math.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]' -Os关键标志:EXPORTED_FUNCTIONS — 指定要导出的函数,-Os — 按大小优化。生成的 math.wasm 即可在 Swift 中加载。
在 Swift 中加载模块
在 iOS/macOS 上,使用 WasmKit 或 WKWebView 处理 Wasm。我们来看高级方案——使用 WasmKit 进行直接内存管理。
import WasmKit
guard let wasmData = try? Data(contentsOf: URL(fileURLWithPath: "math.wasm")) else { fatalError("无法加载 wasm")}
let module = try WasmModule(data: wasmData)let engine = WasmEngine()let instance = try engine.instantiate(module: module)
// 获取导出的函数guard let addFunc = instance.export("add")?.function else { fatalError("未找到 add 函数")}注意:WasmKit 需要手动管理导入和内存。这提供了完全控制,但增加了复杂性。
直接内存操作:传递数组和字符串
Wasm 线性内存
WebAssembly 使用线性内存(linear memory)——一个扁平的字节数组。要传递复杂结构(数组、字符串),您必须在 Wasm 内部分配内存并复制数据。
考虑一个示例:将整数数组从 Swift 传递到计算总和的 Wasm 函数。
// sum_array.cint sum_array(int* arr, int len) { int sum = 0; for (int i = 0; i < len; i++) { sum += arr[i]; } return sum;}编译时导出 malloc 和 free:
emcc sum_array.c -o sum_array.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_sum_array", "_malloc", "_free"]' -s ALLOW_MEMORY_GROWTH=1在 Swift 中分配内存、复制数据并调用函数:
let numbers = [10, 20, 30, 40, 50]let count = numbers.count
// 为数组分配内存(每个 int 4 字节)guard let mallocFunc = instance.export("malloc")?.function else { return }let ptrResult = mallocFunc.call(Int32(count * MemoryLayout<Int32>.size))guard let ptr = ptrResult?.i32 else { return }
// 将数据复制到 Wasm 内存let memory = instance.memory!for i in 0..<count { let offset = ptr + Int32(i * MemoryLayout<Int32>.size) memory.store(offset: Int(offset), value: Int32(numbers[i]))}
// 调用 sum_arrayguard let sumArrayFunc = instance.export("sum_array")?.function else { return }let result = sumArrayFun