Portals は、Swift で構築された Mac アプリです。これはオープン ソースであり、 Ockam Rust ライブラリを使用して、エンドツーエンドで暗号化された Ockam ポータルを介して Mac から友人と TCP または HTTP サービスをプライベートに共有します。共有サービスがローカルホストに表示されます。
この投稿では、SwiftUI macOS アプリが Rust コードとどのように対話するかを詳しく説明します。
興味がある場合は、Portals for Mac を試してみてください。詳細については、この記事をご覧ください。インストールは次のように Homebrew を使用して行われます。
brew install build-trust/ockam/portals
アプリケーションの動作を示す 2 分間のビデオは次のとおりです。
ポータル機能は、Ockam Rust ライブラリにすでに実装されていました。私たちは、優れた macOS ネイティブ エクスペリエンスを作成することに取り組みました。
アプリを構築する最初の試みは、Tauri を使用することでした。私たちは Ockam Rust ライブラリを使用したいと考えており、チームのほとんどのメンバーが Rust での構築に慣れているため、これは理にかなっています。この最初のバージョンは構築が簡単で、必要な基本機能がすべて備えられていました。ただし、アプリの使用感はあまり良くありませんでした。 Tauri は、メニューがどのようにレンダリングされるか、およびユーザーがメニューを操作したときに何が起こるかについて、最小限の制御しか提供しませんでした。 macOS Sonoma に組み込まれている非常に使いやすいメニューバー項目と比較すると、このバージョンのアプリは 10 年前のバージョンの macOS に属しているように感じられました。
私たちが望むリッチなエクスペリエンスを実現するには、SwiftUI を使用してアプリを構築する必要があることに気付きました。
残念ながら、Swift と Rust を統合する、両方の長所を生かした既製のソリューションは見つかりませんでした。 Rust の安全性と、SwiftUI のリッチな macOS ネイティブ エクスペリエンスです。さらに詳しく調べた結果、C-89 を使用して 2 つを接続できることがわかりました。 Rust は C 呼び出し規約と互換性があり、Swift は C-89 のスーパーセットである Objective-C と相互運用可能です。
Swift から見えるようにする必要がある Rust データ構造を 2 回書きました。 1 つのバージョンは Rust で慣用的であり、使いやすいです。もう 1 つのバージョンは C と互換性があり、ポインターと malloc で手動で割り当てられるメモリを使用します。また、unsafe Rust で raw ポインターを使用して慣用的なデータ構造を C 互換バージョンに変換する、いくつかの C 互換 API も公開しました。最後に、cbindgen ライブラリを使用して C ヘッダーを自動的に生成しました。
Swift 側では、C API を直接呼び出すこともできましたが、C データ構造は Swift の第一級市民ではありません。これにより、SwiftUI コード内で慣用的に使用することが難しくなります。代わりに、Swift でデータ構造を複製し、C と Swift の間で変換することを選択しました。これは面倒に思えるかもしれませんが、実際には、共有状態はそれほど頻繁には変化しません。 if let ...
、 ForEach
、 enum
などの構造を使用して SwiftUI でコンポーネントをすばやく構築できる機能は非常に便利で、トレードオフの価値があります。
同じ構造の 4 つの形式の例を次に示します。
// Rust idiomatic structure #[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct LocalService { pub name: String, pub address: String, pub port: u16, pub shared_with: Vec<Invitee>, pub available: bool, } // Rust C-compatible structure #[repr(C)] pub struct LocalService { pub(super) name: *const c_char, pub(super) address: *const c_char, pub(super) port: u16, pub(super) shared_with: *const *const Invitee, pub(super) available: u8, } // Generated C header structure typedef struct C_LocalService { const char *name; const char *address; uint16_t port; const struct C_Invitee *const *shared_with; uint8_t available; } C_LocalService; // Swift idiomatic structure class LocalService { let name: String @Published var address: String? @Published var port: UInt16 @Published var sharedWith: [Invitee] @Published var available: Bool }
Swift アプリはコンパイル時に Rust ライブラリに静的にリンクされます。データ フローはシンプルです。UI インタラクションは C API を呼び出すことによってアクションとして Swift から Rust に送信され、変更イベントは Rust によってのみ発行され、UI の更新につながるコールバックを使用して Swift に通知されます。
SwiftUI ビュー内のほとんどのコードは、他の SwiftUI アプリケーションとまったく同じように見えます。
VStack(alignment: .leading, spacing: 0) { Text(service.sourceName).lineLimit(1) HStack(spacing: 0) { Image(systemName: "circle.fill") .font(.system(size: 7)) .foregroundColor( service.enabled ? (service.available ? .green : .red) : .orange) if !service.enabled { Text(verbatim: "Not connected") } else { if service.available { Text(verbatim: service.address.unsafelyUnwrapped + ":" + String(service.port)) } else { Text(verbatim: "Connecting") } } } ...
さらに詳しく知りたい場合は、 ockam_app_libクレートとSwift のポータル アプリのコードをチェックしてください。 Swift フォルダー内のMakefile は、すべてがどのように構築されリンクされているかを調べるのにも適した場所です。
Portals for Macの Swift または Rust コードに貢献することに興味がある場合は、毎週新しい優れた最初の号を追加し、新しい貢献者を支援することに喜びを感じています。コントリビューターの Discordに参加してください。
ここにも登場します。