You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI结合CocoaMQTT:无需按钮实现自动连接与消息接收

SwiftUI中CocoaMQTT自动连接订阅的正确实现

问题根源

你之前的onAppear代码存在两个核心问题:

  1. 执行顺序错误:连接MQTT后立刻调用订阅,但此时连接还未建立完成,导致订阅请求无效触发协议错误。
  2. 状态管理缺失: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 08:36:10