如何使用闭包在 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
@Published
的 connected
属性,然后更新视图模型的 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")
}
}
我的基本要求是在连接到服务器时监听 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
@Published
的 connected
属性,然后更新视图模型的 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")
}
}