如何使用闭包从 SwiftUI 中具有委托的类接收异步数据?

问题描述

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

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

在示例代码中,一个名为 ClientPackage 的 Swift 包及其类和委托协议。然后是 SwiftUI 用来更新 UI 状态的 viewmodel 类,还有一个实现 LocalClient 库的 ClientPackage 类。基本上 LocalClient 被用作一个抽象的本地包,使 viewmodel 内部的实现更容易。 (我不确定这是否是正确的方法。)因此,当服务器连接到 isConnected 内时,我需要更改 viewmodel 中的 ClientPackage 布尔值。

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

如何使 viewmodel.connect() 中的闭包起作用?

P.S.在实际应用中,我为 ClientPackage 使用了 Starscream 4.0.4 swift 包。

// 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")
        }
    }
}

解决方法

您只需将 connectedLocalClient 属性设为 @Published,然后在更新 isConnected 时更新视图模型的 client.connected 属性。

LocalClient 已经符合 ClientPackageDelegate 并且它的 connected 属性通过委托方法更新,因此您只需要将 connected 的值传播到您的视图模型,更新您的用户界面观察到的 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(我认为这是错误的方法,因为无论如何您已经在视图模型中使用了组合),您只需要将闭包注入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 = $0 })
    }
    
    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")
    }
}