使用 WebAssembly 和 Swift:高级水平

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

WebAssembly 和 Swift 高级入门

\n\n

WebAssembly (Wasm) 是一种低级二进制指令格式,旨在以接近原生的性能在浏览器和服务器上执行。对于从事 iOS 和 macOS 开发的 Swift 开发者来说,集成 Wasm 为将高性能计算(加密、图像处理、模拟)从 Web 环境迁移到原生应用提供了可能性。在本文中,我们将探讨高级技术:从 Swift 直接调用 Wasm 函数、通过 Wasm 线性内存管理内存、优化 ABI(应用程序二进制接口)以及处理复杂数据类型。

\n\n

假设您已经熟悉 Swift 基础知识,并且有将 C/C++ 代码编译为 Wasm 的经验(例如,通过 Emscripten 或 wasi-sdk)。我们将重点放在 Swift (iOS/macOS) 与编译后的 Wasm 模块的集成上,使用最少的依赖项——仅使用 Foundation 标准库和 Swift 的基本内存操作功能。

\n\n

1. 将 C/C++ 代码编译为 WebAssembly 并准备 ABI

\n\n

第一步是准备一个具有明确定义 ABI 的 Wasm 模块。对于 Swift 来说,导出的函数应具有扁平签名(仅限数字:i32、i64、f32、f64),并且不使用复杂结构。以下是用于计算数组总和的 C 函数示例:

\n\n
// sum.c\n#include <stdint.h>\n\nint32_t sum_array(int32_t* arr, int32_t len) {\n    int32_t sum = 0;\n    for (int32_t i = 0; i < len; i++) {\n        sum += arr[i];\n    }\n    return sum;\n}\n\n// 在 Wasm 内部分配内存的函数\nint32_t* allocate_array(int32_t size) {\n    return (int32_t*)malloc(size * sizeof(int32_t));\n}\n
\n\n

使用 wasi-sdk(用于 Wasm 的 CLang)进行编译:

\n\n
clang \\\n  --target=wasm32-wasi \\\n  -O3 \\\n  -nostartfiles \\\n  -Wl,--export=sum_array \\\n  -Wl,--export=allocate_array \\\n  -Wl,--export=__heap_base \\\n  -o sum.wasm sum.c\n
\n\n

标志 --export 明确指定要导出的函数。__heap_base 是 Wasm 的标准符号,指示堆的起始位置,对于从 Swift 管理内存是必需的。

\n\n

2. 在 Swift 中加载 Wasm 模块并执行函数

\n\n

为了在 iOS/macOS 的 Swift 中使用 Wasm,我们可以使用 WebKit 框架(WKWebView)或通过 JavaScriptCore 模块使用底层 C API。然而,对于无需 UI 的纯 Swift 解决方案,更方便的是使用 SwiftWasm 中的 WasmInterpreter 库(可通过 SPM 获取)。我们考虑使用 JavaScriptCore 的方案——它内置于 iOS/macOS 中。

\n\n

2.1. 初始化和加载二进制文件

\n\n
import JavaScriptCore\n\nclass WasmEngine {\n    private let context: JSContext\n\n    init?(wasmData: Data) {\n        guard let ctx = JSContext() else { return nil }\n        self.context = ctx\n\n        // 创建 WebAssembly 对象\n        let wasmObj = ctx.evaluateScript("WebAssembly")\n        guard let wasm = wasmObj else { return nil }\n\n        // 编译模块\n        let compileFunc = wasm.objectForKeyedSubscript("compile")\n        guard let compile = compileFunc else { return nil }\n\n        let promise = compile.call(withArguments: [wasmData])\n        // 处理 promise(为演示简化)\n        context.evaluateScript("""\n            var wasmModule = null;\n            \\(promise?.toString() ?? "").then(m => { wasmModule = m; });\n        """)\n    }\n\n    func callFunction(name: String, args: [JSValue]) -> JSValue? {\n        let instance = context.objectForKeyedSubscript("wasmModule")\n        let exports = instance?.objectForKeyedSubscript("exports")\n        let funcToCall = exports?.objectForKeyedSubscript(name)\n        return funcToCall?.call(withArguments: args)\n    }\n}\n
\n\n

重要提示: 此代码已简化。在实际项目中,请使用 JSVirtualMachin

博客

书籍推荐