paint-brush
加密门户:我们如何创建使用 Rust 的 Swift 应用程序经过@ockam
10,773 讀數
10,773 讀數

加密门户:我们如何创建使用 Rust 的 Swift 应用程序

经过 Ockam5m2024/01/01
Read on Terminal Reader

太長; 讀書

使用 Swift 构建的 Portals for Mac 应用程序如何使用 Ockam Rust 库通过端到端加密门户与您的朋友私下共享服务。
featured image - 加密门户:我们如何创建使用 Rust 的 Swift 应用程序
Ockam HackerNoon profile picture
0-item
1-item

Portals 是一款用 Swift 构建的 Mac应用程序。它是开源的,并使用Ockam Rust 库通过端到端加密的 Ockam 门户与您的朋友私下共享 Mac 上的 TCP 或 HTTP 服务。共享服务出现在他们的本地主机上!


在这篇文章中,我们将深入探讨 SwiftUI macOS 应用程序如何与 Rust 代码交互。


如果您有兴趣尝试 Mac 版 Portals。您可以在本文中了解更多信息,并使用 Homebrew 进行安装,如下所示:


 brew install build-trust/ockam/portals


以下是该应用程序实际运行的 2 分钟视频:

斯威夫特<>铁锈

Portals 功能已在 Ockam Rust 库中实现。我们致力于打造出色的 macOS 原生体验。


我们构建该应用程序的第一次尝试是使用 Tauri。这是有道理的,因为我们想使用 Ockam rust 库,而且我们团队中的大多数人都习惯用 Rust 构建东西。第一个版本很容易构建,并且具有我们想要的所有基本功能。然而,使用该应用程序的体验并不好。 Tauri 只为我们提供了对菜单呈现方式以及用户与菜单交互时发生的情况的最小控制。与 macOS Sonoma 中内置的超级易于使用的菜单栏项目相比,这个版本的应用程序感觉像是 10 年前的 macOS 版本。


我们意识到,要获得我们想要的丰富体验,我们必须使用 SwiftUI 构建应用程序。


不幸的是,我们找不到现成的解决方案来集成 Swift 和 Rust,这将为我们带来两全其美的效果; Rust 的安全性以及 SwiftUI 丰富的 macOS 原生体验。经过更多挖掘后,我们意识到可以使用 C-89 将两者连接起来。 Rust 与 C 调用约定兼容,Swift 与 Objective-C(C-89 的超集)可互操作。


Swift 和 Rust 如何通信



我们编写了需要对 Swift 可见两次的 Rust 数据结构。其中一个版本是 Rust 惯用的并且易于使用。另一个版本与 C 兼容,使用指针和通过 malloc 手动分配的内存。我们还公开了一些 C 兼容的 API,它们在不安全的 rust 中使用原始指针将惯用的数据结构转换为其 C 兼容版本。最后,我们在 cbindgen 库的帮助下自动生成了一个 C 头文件。


在 Swift 方面,我们可以直接调用 C API,但 C 数据结构并不是 Swift 中的一等公民。这使得它们更难在 SwiftUI 代码中惯用地使用。相反,我们选择复制 Swift 中的数据结构并在 C 和 Swift 之间进行转换。这可能看起来很麻烦,但实际上,共享状态不会经常改变。使用if let ...ForEachenum等结构在 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 crate 和Swift 中的 Portals 应用程序的代码。 swift 文件夹中的Makefile也是探索所有内容如何构建和链接在一起的好地方。


如果您有兴趣为Portals for Mac的 Swift 或 Rust 代码做出贡献,我们每周都会添加新的Good First 问题,并且乐于帮助新的贡献者。加入我们的贡献者不和谐


也出现在这里