如何使用闭包在 SwiftUI 中使用委托接收来自 class 的异步数据?

How to use closure to receive async data from a class with delegate in SwiftUI?

我的基本要求是在连接到服务器时监听 swift 包的变量值,并在视图模型中更改相同类型的发布值。

下面的代码是为解释场景而构建的示例代码。此代码无法正常工作,因为连接服务器时 isConnected 值未更新。

在示例代码中,一个名为 ClientPackage 的 Swift 包及其 class 和委托协议。然后 ViewModel class SwiftUI 用于更新 UI 的状态,还有 LocalClient class 实现ClientPackage 图书馆。基本上,LocalClient 用作抽象的本地包,以便在 ViewModel 中更容易地实现相同的功能。 (我不确定这是否是正确的方法。)所以当服务器在 ClientPackage.

内连接时,我需要在 ViewModel 中更改 isConnected 布尔值

所以我想修复此代码以更新 isConencted 更新的值。 LocalClient 中的 connected 变量已通过委托方法正确设置。

如何使 ViewModel.connect() 中的闭包工作?

P.S。在真实的应用程序中,我使用 Starscream 4.0.4 swift 包 ClientPackage.

// SocketClient.swift
// Xcode 12.3

import Foundation

class ViewModel: ObservableObject {
    
    var client: LocalClient?

    @Published var isConnected: Bool?
    
    let token = "12345678" // This is fetched async in real scenario
    
    init() {
        client = LocalClient(token: token)
    }
    
    func connect() {
        client?.connect() { connected in
            self.isConnected = connected
        }
    }
    
}

// Assuming code below is from a local swift package and can be modified

class LocalClient: NSObject {
    
    var coreClient: ClientPackage?
    var connected = false
    
    init(token: String) {
        super.init()
        coreClient = ClientPackage(token: token)
        coreClient!.delegate = self
    }
    
    func connect(_ completion: @escaping (Bool) -> Void) {
        coreClient?.connectToServer()
        completion(connected)
    }
}

extension LocalClient: ClientPackageDelegate {
    func connectedToServer(_ client: ClientPackage) {
        connected = true
        print("Connected to server")
    }
    
    func disconnectedFromServer(_ client: ClientPackage) {
        connected = false
        print("Disconnected from server")
    }
}

// Assuming code below is from a swift package and cannot be modified

// import Starscream

open class ClientPackage {
    open weak var delegate: ClientPackageDelegate?
    open var socketConnected: Bool?
    open var token: String?
    
    public init(token: String) {
        self.token = token
        self.socketConnected = false
    }
    
    open func didConnect() {
        self.delegate?.connectedToServer(self)
        self.socketConnected = true
    }
    
    open func didDisconnect() {
        self.delegate?.disconnectedFromServer(self)
        self.socketConnected = false
    }
    
    open func connectToServer() {
        // This method connect client to server asynchronously over websocket
        // When connection stable, self.didConnect is called
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.didConnect()
        }
    }
    
    open func disconnectFromServer() {
        // This method disconnect the connection
        // When connection stable, self.didDisconnect is called
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.didDisconnect()
        }
    }
}

public protocol ClientPackageDelegate: NSObjectProtocol {
    func connectedToServer(_ client: ClientPackage)
    func disconnectedFromServer(_ client: ClientPackage)
}
// ContentView.swift

import SwiftUI

struct ContentView: View {
    @StateObject var vm = ViewModel()
    var connected: Bool { vm.isConnected ?? false }
    var body: some View {
        VStack {
            Button(connected ? "Disconnected" : "Connected"){
                connected ? vm.disconnect() : vm.connect()
            }
            Text(connected ? "Connected" : "Disconnected")
        }
    }
}

您只需创建 LocalClient @Publishedconnected 属性,然后更新视图模型的 isConnected 属性每当 client.connected 更新时。

LocalClient 已经符合 ClientPackageDelegate 并且它的 connected 属性 通过委托方法更新,所以你只需要传播 [=12 的值=] 到您的视图模型,以更新您的 UI 观察到的 isConnected 属性。

class ClientViewModel: ObservableObject {
    
    var client: LocalClient?

    @Published var isConnected: Bool = false
    
    let token = "12345678" // This is fetched async in real scenario
    
    init() {
        client = LocalClient(token: token)
        client?.$connected.assign(to: &$isConnected)
    }
    
    func connect() {
        client?.connect() { connected in
            self.isConnected = connected
        }
    }
    
}

// Assuming code below is from a local swift package and can be modified

class LocalClient: NSObject {
    
    var coreClient: ClientPackage?
    @Published var connected = false
    
    init(token: String) {
        super.init()
        coreClient = ClientPackage(token: token)
        coreClient!.delegate = self
    }
    
    func connect(_ completion: @escaping (Bool) -> Void) {
        coreClient?.connectToServer()
        completion(connected)
    }
}

如果你想要基于闭包的行为而不是使用 Combine(我认为这是错误的方法,因为你已经在你的视图模型中使用了 Combine),你只需要注入一个关闭到 LocalClient,只要连接状态发生变化,您就可以从委托方法中执行它。

class ClientViewModel: ObservableObject {
    
    var client: LocalClient?

    @Published var isConnected: Bool = false
    
    let token = "12345678" // This is fetched async in real scenario
    
    init() {
        client = LocalClient(token: token, connectionStateChanged: { [weak self] in self?.isConnected = [=11=] })
    }
    
    func connect() {
        client?.connect() { connected in
            self.isConnected = connected
        }
    }
    
}

// Assuming code below is from a local swift package and can be modified

class LocalClient: NSObject {
    
    let coreClient: ClientPackage
    var connected = false
    let connectionStateChanged: (Bool) -> Void
    
    init(token: String, connectionStateChanged: @escaping (Bool) -> Void) {
        self.connectionStateChanged = connectionStateChanged
        coreClient = ClientPackage(token: token)
        super.init()
        coreClient.delegate = self
    }
    
    func connect(_ completion: @escaping (Bool) -> Void) {
        coreClient.connectToServer()
        completion(connected)
    }
}

extension LocalClient: ClientPackageDelegate {
    func connectedToServer(_ client: ClientPackage) {
        self.connected = true
        connectionStateChanged(true)
        print("Connected to server")
    }
    
    func disconnectedFromServer(_ client: ClientPackage) {
        self.connected = false
        connectionStateChanged(false)
        print("Disconnected from server")
    }
}