【问题标题】:How to use closure to receive async data from a class with delegate in SwiftUI?如何在 SwiftUI 中使用闭包从具有委托的类接收异步数据?
【发布时间】:2021-04-10 03:27:02
【问题描述】:

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

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

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

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

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

附注在实际应用中,我使用 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")
        }
    }
}

【问题讨论】:

    标签: ios swift asynchronous delegates closures


    【解决方案1】:

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

    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(我认为这是错误的做法,因为你已经在视图模型中使用了Combine),你只需要将闭包注入@987654333 @,只要连接状态发生变化,您就会从您的委托方法中执行它。

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

    【讨论】:

    • 我不想在本地包中使用 observableobject 类。我想使用闭包与本地包进行通信。
    • @NikzJon 你不需要ObservableObject,你只需要@Published。您是否使用本地软件包定位 Combine 的原因吗?我看不出有任何其他理由让您希望基于闭包的行为优于 Combine,尤其是当您在视图模型中使用 Combine 时。
    • 是的,兼容性是一个原因。但我也不太熟悉复杂的出版商等等。我有很长的路要走,这只是冰堡的一角。所以我认为坚持关闭可能更容易变得复杂。
    • @NikzJon 使用基于闭包的解决方案更新了我的答案,但我建议您改用 Combine 解决方案。它不使用任何复杂的发布者,并产生更简洁和更清晰的代码。因此,除非您需要以
    • 感谢您的解决方案。关闭效果很好!但是在阅读了您的 cmets 之后,我决定使用 combine 。再次感谢。
    猜你喜欢
    • 2019-02-27
    • 1970-01-01
    • 1970-01-01
    • 2017-06-23
    • 1970-01-01
    • 2017-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多