SwiftUI结合CocoaMQTT:无需按钮实现自动连接与消息接收
SwiftUI中CocoaMQTT自动连接订阅的正确实现
问题根源
你之前的onAppear代码存在两个核心问题:
- 执行顺序错误:连接MQTT后立刻调用订阅,但此时连接还未建立完成,导致订阅请求无效触发协议错误。
- 状态管理缺失:
windAngle是普通变量,数据更新无法触发SwiftUI视图刷新;且未在连接成功的回调中执行订阅逻辑。
最优实现方案:基于ViewModel封装MQTT逻辑
将MQTT的连接、订阅、消息接收逻辑封装到ObservableObject类型的ViewModel中,既能模拟UIKit的代理回调流程,又能适配SwiftUI的状态响应机制。
1. 定义ViewModel
import Foundation import CocoaMQTT class MqttViewModel: ObservableObject { // 发布风速数据,供SwiftUI视图监听 @Published var windAngleString: String = "" private let mqttClient: CocoaMQTT init() { // 初始化MQTT客户端,确保ClientID唯一 let clientID = "Navigation-\(UUID().uuidString)" mqttClient = CocoaMQTT(clientID: clientID, host: "raspberrypi.local", port: 1883) // 提前设置所有回调(必须在connect之前设置) setupMqttCallbacks() } private func setupMqttCallbacks() { // 连接确认回调:连接成功后再订阅主题 mqttClient.didConnectAck = { [weak self] mqtt, ack in guard let self = self else { return } if ack == .accept { print("MQTT连接成功") self.mqttClient.subscribe("windData") } else { print("MQTT连接失败,错误码:\(ack.rawValue)") } } // 消息接收回调:更新发布的风速数据 mqttClient.didReceiveMessage = { [weak self] mqtt, message, id in guard let self = self, let messageString = message.string else { return } self.windAngleString = messageString print("收到风速数据:\(messageString)") } // 可选:连接断开回调 mqttClient.didDisconnect = { mqtt, err in print("MQTT断开连接:\(err?.localizedDescription ?? "无错误")") } } // 对外暴露连接方法 func connect() { _ = mqttClient.connect() } // 对外暴露断开连接方法 func disconnect() { mqttClient.disconnect() } }
2. 修改ContentView
import SwiftUI struct ContentView: View { // 注入ViewModel,监听状态变化 @StateObject private var mqttVM = MqttViewModel() var body: some View { VStack { // 将风速数据传递给风速仪视图 AnemometerView(windAngle: mqttVM.windAngleString) Button("断开连接") { mqttVM.disconnect() } } .onAppear { // 视图出现时自动触发连接 mqttVM.connect() } .onDisappear { // 视图消失时断开连接,释放资源 mqttVM.disconnect() } } } // 假设的风速仪视图,接收风速数据 struct AnemometerView: View { let windAngle: String var body: some View { Text("当前风速角度:\(windAngle)") .font(.title) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
关键细节说明
- 回调顺序:必须在调用
connect()前设置所有MQTT回调,否则无法捕获连接成功的事件。 - 状态响应:用
@Published标记数据属性,结合@StateObject管理ViewModel,确保数据更新时视图自动刷新。 - 生命周期管理:在
onDisappear中断开连接,避免后台无效连接占用资源。 - ClientID唯一性:通过UUID生成唯一ClientID,避免多个客户端使用相同ID被MQTT服务器强制断开。
内容的提问来源于stack exchange,提问作者bacata.borisov
相关产品推荐
相关产品推荐

