Работа с WebAssembly и Swift: руководство для iOS/macOS разработчиков

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем iOS/macOS разработчику WebAssembly?

WebAssembly (Wasm) — это бинарный формат инструкций, который изначально создавался для браузеров, но быстро нашёл применение в нативной разработке. Для Swift-разработчиков под iOS и macOS интеграция WebAssembly открывает уникальные возможности: вы можете переиспользовать высокопроизводительный код на C/C++, Rust или Go, не переписывая его на Swift, и запускать его с почти нативной скоростью. Это особенно актуально для работы с графикой, криптографией, симуляциями и сложными алгоритмами.

В этой статье мы разберём, как скомпилировать код в Wasm, интегрировать его в Swift-проект для iOS/macOS, передавать данные и обрабатывать ошибки. Все примеры написаны на Swift 5.9+ и ориентированы на Xcode 15+.

1. Компиляция кода в WebAssembly для Swift-проекта

Первый шаг — получить Wasm-модуль. Самый популярный способ — скомпилировать код на C/C++ с помощью Emscripten или WasmEdge. Рассмотрим пример на C.

Создайте файл math_ops.c:

// math_ops.c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

EMSCRIPTEN_KEEPALIVE
double multiply(double a, double b) {
    return a * b;
}

EMSCRIPTEN_KEEPALIVE
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

Скомпилируйте его в Wasm с помощью Emscripten (установите через Homebrew: brew install emscripten):

emcc math_ops.c -o math_ops.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add", "_multiply", "_factorial"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -O3

В результате вы получите файл math_ops.wasm и math_ops.js (js-обёртка не понадобится, мы будем работать напрямую с Wasm).

2. Интеграция Wasm-модуля в Swift (iOS/macOS)

Для работы с WebAssembly в нативном Swift мы будем использовать фреймворк WasmKit или SwiftWasm. Рекомендуем WasmKit — он поддерживает iOS 15+ и macOS 12+.

2.1. Установка WasmKit через Swift Package Manager

Добавьте зависимость в Package.swift или через Xcode: File → Add Packages → https://github.com/swiftwasm/WasmKit.

2.2. Загрузка и выполнение Wasm-модуля

Создайте Swift-файл WasmEngine.swift:

import WasmKit
import Foundation

class WasmEngine {
    private var instance: Instance?
    private var store: Store?
    
    /// Загружает Wasm-модуль из файла
    func loadModule(from url: URL) throws {
        let data = try Data(contentsOf: url)
        let module = try Module(data: data)
        let engine = Engine()
        store = Store(engine: engine)
        
        let imports = Imports()
        // Здесь можно добавить импорт функций из Swift (см. раздел 3)
        
        instance = try Instance(module: module, imports: imports, store: store!)
    }
    
    /// Вызывает экспортированную функцию
    func callFunction(_ name: String, args: [Value]) throws -> [Value] {
        guard let instance = instance else {
            throw WasmError.moduleNotLoaded
        }
        let function = instance.exports[name] as! Function
        return try function(args)
    }
}

enum WasmError: Error {
    case moduleNotLoaded
    case functionNotFound
}

2.3. Использование в SwiftUI-приложении

import SwiftUI

struct ContentView: View {
    @State private var result: String = "0"
    private let engine = WasmEngine()
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Результат: \(result)")
                .font(.title)
            
            Button("Вычислить факториал 5") {
                do {
                    let wasmURL = Bundle.main.url(forResource: "math_ops", withExtension: "wasm")!
                    try engine.loadModule(from: wasmURL)
                    let output = try engine.callFunction("factorial", args: [.i32(5)])
                    if case let .i32(value) = output.first {
                        result = "\(value)"
                    }
                } catch {
                    result = "Ошибка: \(error.localizedDescription)"
                }
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
        }
        .padding()
    }
}

Не забудьте добавить файл math_ops.wasm в Bundle вашего приложения (Build Phases → Copy Bundle Resources).

3. Передача сложных данных между Swift и WebAssembly

В Wasm нет прямого доступа к памяти Swift, поэтому для передачи строк, массивов или структур используется линейная память модуля. Рассмотрим пример передачи строки.

3.1. Экспорт функции для работы со строкой (C-код)

// string_ops.c
#include <emscripten.h>
#include <string.h>

EMSCRIPTEN_KEEPALIVE
int get_string_length(char* str) {
    return strlen(str);
}

EMSCRIPTEN_KEEPALIVE
void to_uppercase(char* str) {
    for (int i = 0; str[i]; i++) {
        if (str[i] >= 'a' && str[i] <= 'z') {
            str[i] -= 32;
        }
    }
}

Скомпилируйте: emcc string_ops.c -o string_ops.wasm -s WASM=1 -O3

3.2. Работа с памятью Wasm из Swift

extension WasmEngine {
    /// Записывает строку в память Wasm-модуля
    func writeString(_ str: String) throws -> Int32 {
        guard let instance = instance, let store = store else {
            throw WasmError.moduleNotLoaded
        }
        let memory = instance.exports["memory"] as! Memory
        let bytes = Array(str.utf8)
        let ptr = try instance.exports["malloc"] as! Function([.i32(Int32(bytes.count + 1))])
        guard case let .i32(address) = ptr.first else {
            throw WasmError.allocationFailed
        }
        // Копируем байты в память
        var data = bytes + [0] // нуль-терминатор
        data.withUnsafeMutableBytes { rawPtr in
            memory.data.withUnsafeMutableBytes { wasmPtr in
                wasmPtr.copyMemory(from: rawPtr, byteCount: data.count)
            }
        }
        return address
    }                

Блоги

Рекомендаций по книгам