Swift Digest
Blog | Swift.org Blog

Goodnotes を Swift と WebAssembly で Web へ

Bringing Goodnotes to the web with Swift and WebAssembly

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

なぜ Web で Swift を選んだのか

2021年に Goodnotes を Web へ展開しようと決めたとき、チームは10年以上かけて蓄積した数百万行の Swift コードを抱えていました。デジタルインクの描画、ドキュメント同期、CRDT(Conflict-Free Replicated Data Types)による競合解決、コンテンツ検索やインデックス化といった、無数の改良と最適化が詰まったコードです。

リアルタイムのインク描画では60FPS 超を維持する必要があり、性能が決定的に重要でした。JavaScript での書き直し、Flutter、Kotlin Multiplatform のいずれを選んでも、描画エンジンをゼロから書き直すことになり、Web 版のリリースが何年も遅れるうえ、プラットフォーム間で挙動の差異が生まれることは避けられませんでした。

解決策となったのが SwiftWasm です。性能要求の厳しい手書きコンポーネントでプロトタイプを作って検証したところ、結果は十分に有望で、この道に踏み切りました。最も大きな利点はコードの再利用そのものではなく、挙動の一貫性が保証されることでした。ユーザーが iPad で描いたストロークを後から Web で開いても、まったく同じ曲線・筆圧・インクの流れが再現されます。同じアルゴリズムを2回丁寧に実装し直したからではなく、文字どおり同じ Swift コードが両プラットフォームで動いているからです。

技術アーキテクチャ

アーキテクチャは、プラットフォーム固有の UI コンポーネントと、共有されるビジネスロジックを明確に分離する設計になっています。

共有されるコアと、コード共有の規模

中核を担うのは次の3つです。

コード共有の規模は、Web 版の Swift コードベース全体が220万行、うち共有コードが147万行(Web アプリの66%、iOS アプリの34%)に達します。行数は最良の指標ではないものの、共有しているビジネスロジックと描画エンジンの大きさを示しています。

最終的な WebAssembly バイナリは約50MB で、Brotli 圧縮により12MB まで小さくなります。読み込みの高速化とキャッシュには Service Worker を利用しています。

JavaScript 相互運用とプラットフォーム差異

Swift と JavaScript のシームレスな相互運用には JavaScriptKit を使い、コアロジックを Swift に保ったまま既存の Web エコシステムと統合しています。

iOS と WebAssembly でコードを共有する際には、いくつかの注意点がありました。

WASI Threads によるマルチスレッド

最も重要な技術的成果のひとつが、WASI(WebAssembly System Interface)ThreadsWeb WorkerSharedArrayBuffer による真の並列処理の実現です。これにより、手書き認識をバックグラウンドの Web Worker で実行し、ドキュメントのインデックス化でメインスレッドをブロックせず、複雑な処理中も60FPS 超の滑らかな描画を維持できます。

ここで決定的な役割を果たしたのが Swift Concurrency の Custom Actor Executors(SE-0392)です。JavaScript オブジェクトは生成元のスレッドに isolate されるため、Swift のアクターをどのスレッドで実行するかを精密に制御する必要がありました。JavaScriptKit は専用 Web Worker 向けの SerialExecutor を生成する API を提供しており、特定のアクターを特定の Web Worker に固定できます。これにより、手書き認識のような計算負荷の高いタスクはバックグラウンドで動かしつつ、UI 操作はメインスレッドに留め、バックグラウンドスレッドからも JavaScript オブジェクトにアクセスできるようになっています。

このマルチスレッド化により、Interaction to Next Paint(INP)が2倍以上改善し、複雑な操作中の UI 応答性が大きく向上しました。

一方で、SharedArrayBuffer を使うにはCross-Origin Isolation というモダンブラウザのセキュリティ要件を満たす必要があり、アプリにいくらかの複雑さを加えます。この要件を満たせないアプリケーションでは、シングルスレッドの協調的な並行実行も依然として有効な選択肢だとされています。

開発体験とツール

ツールのエコシステムは成熟しており、堅実な開発体験が得られたと述べられています。

得られた知見と今後

WebAssembly 関連の変更はすべて upstream に取り込まれ、WebAssembly は Swift 6.2 から公式にサポートされるプラットフォームになりました。これにより、他のチームも Goodnotes の成功を支えたのと同じツールと言語機能の恩恵を受けられます。

Swift on WebAssembly は複雑なアプリケーションでも本番運用に耐えると結論づけたうえで、同じ道を検討するチームへの推奨事項が挙げられています。

iOS を動かす言語が、いまや高速・安全・保守しやすい Web 体験も生み出せる——Swift のクロスプラットフォーム化が進む様子を示す事例です。

関連リンク