问题描述
我的基本要求是在连接到服务器时监听 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")
}
}
}
解决方法
您只需将 connected
的 LocalClient
属性设为 @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")
}
}