SwiftStreamManager

注意 (2024年3月)

虽然这个库可以按预期工作,但如果在经常唤醒和休眠的笔记本电脑上使用,经过很长一段时间后,Swift websocket 连接可能会失败,需要自动重启 golang 二进制文件。 这是底层 macOS Swift websockets 库的一个问题。 请考虑使用 calmdocs/SwiftPollManager,它通过 http 长轮询进行通信。

关于

作为构建 electron 应用或使用 GUI 工具包的替代方案,可以运行嵌入在原生 macOS SwiftUI 应用中的 golang 二进制文件。 golang 二进制文件和 SwiftUI 应用通过加密的 websocket 消息进行通信。

如果您已经安装了 xCode 和 go,按照示例构建一个新的运行应用程序大约需要 2 分钟。

设置

创建一个新的 macOS Swift Xcode 项目

示例

创建我们的 go 二进制文件 (用于 amd64 和 arm64)

运行以下命令

将我们刚刚构建的 gobinary-darwin-amd64 和 gobinary-darwin-arm64 文件拖到我们新的 macOS Swift xCode 项目中。

在我们新的 Swift xCode 项目中,将 ContentView.swift 替换为以下代码

import SwiftUI

import SwiftStreamManager
import SwiftWebSocketManager
import SwiftProcessManager

public struct CustomStorageItem: Codable, Identifiable {
    public var id: Int64
    
    let error: String?
    let name: String
    let status: String
    let progress: Double

    enum CodingKeys: String, CodingKey {
        case id = "ID"
        case error = "Error"
        case name = "Name"
        case status = "Status"
        case progress = "Progress"
    }
}

struct ContentView: View {
    @ObservedObject var itemsStore: ItemsStore = ItemsStore()
     
    var body: some View {
        List {
            HStack {
                Button(action: {
                    itemsStore.sm.publish(
                        itemsStore.sendStream!,
                        type: "addItem",
                        id: "",
                        data: ""
                    )
                }, label: {
                    Image(systemName: "plus")
                })
                Spacer()
            }
            ForEach(itemsStore.items) { item in
                HStack{
                    Text("\(item.name) (\(item.status))")
                    Spacer()
                    Button(action: {
                        itemsStore.sm.publish(
                            itemsStore.sendStream!,
                            type: "deleteItem",
                            id: String(item.id),
                            data: ""
                        )
                    }, label: {
                        Image(systemName: "trash")
                    })
                }
            }
        }
    }
}

class ItemsStore: ObservableObject {
    private let binName = SystemArchitecture() == "arm64" ? "gobinary-darwin-arm64" : "gobinary-darwin-amd64"

    @Published var items: [CustomStorageItem] = [CustomStorageItem]()
    
    // StreamManager
    @Published var sm: StreamManager
    @Published var sendStream: WebSocketStream?
  
    init() {
      
        // Initialise StreamManager
        self.sm = StreamManager(
            KeyExchange_Curve25519_SHA256_HKDF_AESGCM,
            baseURL: URL(string: "ws://127.0.0.1")!,
            port: Int.random(in: 8001..<9000)
        )
        self.sm.addPIDAsArgument("pid")
        self.sm.addBearerTokenAsArgument("token")
        self.sm.addPortAsArgument("port")
        
        // Start binary and subscribe to a websocket stream
        self.sm.subscribeWithBinary(
            streamPath: "/ws/0",
            binName: self.binName,
            withPEMWatcher: true,
            standardOutput: { result in
                print(result)
            },
            messages: { message in

                // Decrypt and decode
                guard let newItems: [CustomStorageItem] = try? self.sm.decryptAndDecodeJSON(
                    message: message,
                    auth: self.sm.authTimestamp  // Use the current time since 1970 in milliseconds as the default key exchange auth key.
                ) else {
                    return
                }

                // Update items
                DispatchQueue.main.async {
                    self.items.replaceInPlace(items: newItems)
                }
            },
            onConnected: {
                
                // Create a second (sending) stream subscribed to a different path
                DispatchQueue.main.async {
                    self.sendStream = try! self.sm.stream(streamPath: "/ws/1")
                    self.sm.subscribe(self.sendStream!,
                        //messages: { message in
                        //    print(message)
                        //},
                        errors: { err in
                            print(err)
                        }
                    )
                }
            }
        )
    }
}

安全

在创建此库时,我们尽可能保守。 请参阅 calmdocs/SwiftKeyExchange package page 上提供的安全详细信息。 请注意,您使用此库和此 repo 中的代码的风险自负,我们对其使用不承担任何责任。