IoT Eğitimleri

CocoaMQTT ve SwiftUI ile iOS Swift MQTT Client

Adım adım iOS Swift MQTT eğitimi: CocoaMQTT'yi Swift Package Manager ile ekleyin, bir ObservableObject içine sarın, canlı sensör verisini SwiftUI'da gösterin ve TLS üzerinden komut publish edin.

Bulut broker'dan gelen canlı sensör değerlerini ve bir SwiftUI komut düğmesini gösteren iPhone üzerindeki iOS Swift MQTT uygulaması
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bu iOS Swift MQTT eğitimi, canlı sensör topic'lerine subscribe olan ve bir cihaza komut publish eden küçük bir SwiftUI uygulaması geliştirir. CocoaMQTT'yi Swift Package Manager ile ekleyecek, gözlemlenebilir bir model içine saracak, broker.connectmq.com'a TLS üzerinden bağlanacak ve gerçek iPhone'ların ihtiyaç duyduğu yeniden bağlanmaları yöneteceksiniz.

Neye İhtiyacınız Var?

  • Xcode 15 veya üzeri ve iOS 16+ bir simülatör ya da cihaz.
  • CocoaMQTT paketi (MQTT 3.1.1 ve MQTT 5 desteği, açık kaynak, EMQX tarafından sürdürülüyor).
  • Bir broker. Öğrenme amacıyla broker.connectmq.com adresindeki public MQTT test broker'ı, boş olmayan herhangi bir client ID ile anonim client'ları kabul eder. Public alan herkes tarafından okunabildiği için connectmq/demo/<random-id>/... gibi rastgele bir topic öneki kullanın.
  • İsteğe bağlı olarak bir cihazı simüle etmek için Mosquitto CLI araçları.

Uygulama iki topic kullanır:

Topic Yön Payload
<prefix>/sensors/+/temperature cihazdan uygulamaya 21.7
<prefix>/fan/set uygulamadan cihaza ON / OFF

+ wildcard'ı, tek bir subscription'ın her sensörü (kitchen, office, ...) kapsaması anlamına gelir ve uygulama listesini dinamik olarak oluşturur. Ekibiniz bir Android veya platformlar arası sürüm de yayınlıyorsa, aynı topic'ler Kotlin ile Android MQTT client veya bir Flutter MQTT uygulaması ile değişmeden çalışır.

CocoaMQTT'yi Projeye Ekleme

Xcode'da File, Add Package Dependencies seçeneğini seçin ve şunu girin:

https://github.com/emqx/CocoaMQTT.git

2.1.0'dan itibaren "Up to Next Major Version" seçeneğini seçin ve CocoaMQTT kütüphanesini uygulama hedefinize ekleyin. CocoaMQTT soket bağımlılığını otomatik olarak getirir. Projenin GitHub'daki README dosyası resmi CocoaMQTT dokümantasyonudur ve delegate API'sinin tamamını listeler.

App Transport Security istisnasına gerek yoktur: herkesçe güvenilen bir Let's Encrypt sertifikasına sahip bir host'a TLS üzerinden bağlanıyorsunuz; iOS da bunu sistem güven deposuna göre doğrular.

iOS Swift MQTT Modelini Oluşturma

Ağ işlemlerini SwiftUI view'larının dışında tutun. Bir ObservableObject client'a sahip olur, bağlantı durumunu ve okumaları yayınlar ve bir setFan metodu sunar.

MQTTModel.swift:

import Foundation
import CocoaMQTT

@MainActor
final class MQTTModel: ObservableObject {
    @Published var isConnected = false
    @Published var readings: [String: Double] = [:]
    @Published var lastError: String?

    let topicPrefix: String
    private let mqtt: CocoaMQTT

    init(host: String = "broker.connectmq.com",
         port: UInt16 = 8883,
         username: String? = nil,
         password: String? = nil,
         clientID: String? = nil,
         topicPrefix: String? = nil) {
        let suffix = String(UUID().uuidString.prefix(8)).lowercased()
        self.topicPrefix = topicPrefix ?? "connectmq/demo/\(suffix)"
        let id = clientID ?? "ios-\(suffix)"

        mqtt = CocoaMQTT(clientID: id, host: host, port: port)
        mqtt.username = username
        mqtt.password = password
        mqtt.enableSSL = true
        mqtt.keepAlive = 30
        mqtt.cleanSession = true
        mqtt.autoReconnect = true
        mqtt.autoReconnectTimeInterval = 2
        mqtt.maxAutoReconnectTimeInterval = 60

        mqtt.didConnectAck = { [weak self] client, ack in
            Task { @MainActor in
                guard let self else { return }
                self.isConnected = (ack == .accept)
                if ack == .accept {
                    client.subscribe("\(self.topicPrefix)/sensors/+/temperature", qos: .qos1)
                } else {
                    self.lastError = "Connection rejected: \(ack)"
                }
            }
        }

        mqtt.didReceiveMessage = { [weak self] _, message, _ in
            Task { @MainActor in
                guard let self,
                      let text = message.string,
                      let value = Double(text.trimmingCharacters(in: .whitespaces)) else { return }
                // topic: <prefix>/sensors/<name>/temperature
                let parts = message.topic.split(separator: "/")
                guard parts.count >= 2 else { return }
                let name = String(parts[parts.count - 2])
                self.readings[name] = value
            }
        }

        mqtt.didDisconnect = { [weak self] _, error in
            Task { @MainActor in
                self?.isConnected = false
                if let error { self?.lastError = error.localizedDescription }
            }
        }
    }

    func connect() {
        lastError = nil
        if !mqtt.connect() {
            lastError = "Could not start connection"
        }
    }

    func disconnect() {
        mqtt.disconnect()
    }

    func setFan(_ on: Bool) {
        mqtt.publish("\(topicPrefix)/fan/set", withString: on ? "ON" : "OFF", qos: .qos1)
    }
}

Anlaşılmaya değer üç tercih var:

  1. Callback'ler main actor'a geçer. CocoaMQTT closure'larını kendi kuyruğunda tetikler. @Published özelliklerini bir arka plan thread'inden güncellemek SwiftUI uyarılarına ve ara sıra görsel aksaklıklara yol açar; bu yüzden her callback işini Task { @MainActor in ... } içine sarar.
  2. didConnectAck içinde subscribe olmak. cleanSession = true olduğunda broker, bağlantı kesildiğinde subscription'ları unutur. Bağlantı onayının içinde subscribe olmak, her otomatik yeniden bağlanmanın subscription'ları geri yüklemesini sağlar.
  3. Savunmacı ayrıştırma. Public bir topic'e herkes publish edebilir; bu nedenle sayısal olmayan bir payload uygulamayı çökertmek yerine yok sayılır.

SwiftUI Ekranı

ContentView.swift:

import SwiftUI

struct ContentView: View {
    @StateObject private var model = MQTTModel()
    @State private var fanOn = false
    @Environment(\.scenePhase) private var scenePhase

    var body: some View {
        NavigationStack {
            List {
                Section("Connection") {
                    Label(model.isConnected ? "Connected" : "Disconnected",
                          systemImage: model.isConnected ? "checkmark.circle" : "xmark.circle")
                    if let err = model.lastError {
                        Text(err).foregroundStyle(.red).font(.footnote)
                    }
                    Text(model.topicPrefix).font(.caption).textSelection(.enabled)
                }
                Section("Sensors") {
                    if model.readings.isEmpty {
                        Text("Waiting for data...").foregroundStyle(.secondary)
                    }
                    ForEach(model.readings.keys.sorted(), id: \.self) { name in
                        HStack {
                            Text(name.capitalized)
                            Spacer()
                            Text(String(format: "%.1f °C", model.readings[name] ?? 0))
                                .monospacedDigit()
                        }
                    }
                }
                Section("Commands") {
                    Toggle("Fan", isOn: $fanOn)
                        .onChange(of: fanOn) { _, newValue in model.setFan(newValue) }
                        .disabled(!model.isConnected)
                }
            }
            .navigationTitle("iOS MQTT Demo")
        }
        .onAppear { model.connect() }
        .onChange(of: scenePhase) { _, phase in
            switch phase {
            case .active: model.connect()
            case .background: model.disconnect()
            default: break
            }
        }
    }
}

scenePhase işleyicisi iOS'a özgü önemli kısımdır. Uygulamanız arka plana geçtiğinde iOS onu saniyeler içinde askıya alır ve soket sessizce ölür. .background durumunda düzgünce bağlantıyı kesmek ve .active durumunda yeniden bağlanmak, kullanıcılara uygulamayı yeniden açtıkları anda taze ve subscribe olunmuş bir bağlantı sağlar. Burada gösterilen iki parametreli onChange biçimi iOS 17 gerektirir; iOS 16'da tek parametreli closure'ı kullanın.

Uygulamayı Simüle Edilmiş Bir Sensörle Test Etme

Uygulamayı çalıştırın, ekrandaki topic önekini kopyalayın ve bir terminalden okumalar publish edin:

PREFIX="connectmq/demo/1a2b3c4d"   # uygulamada gösterilen öneki yapıştırın

mosquitto_pub -h broker.connectmq.com -p 1883 -i ios-sim-kitchen \
  -t "$PREFIX/sensors/kitchen/temperature" -m "22.8"

mosquitto_pub -h broker.connectmq.com -p 1883 -i ios-sim-office \
  -t "$PREFIX/sensors/office/temperature" -m "20.1"

# Uygulamadan gelen fan komutlarını izle
mosquitto_sub -h broker.connectmq.com -p 1883 -i ios-sim-fan -t "$PREFIX/fan/set" -v

İki sensör de listede görünmeli ve fan anahtarını değiştirmek subscriber'da ON veya OFF yazdırmalıdır. Yazmak yerine tıklamayı tercih ederseniz, ConnectMQ Desktop MQTT explorer ve portaldaki tarayıcı içi client aynı işi görür.

Cihazın Çevrimiçi Durumunu Gösterme

Bir sıcaklık listesi, ancak kullanıcı sensörlerin canlı olduğunu biliyorsa güvenilirdir. MQTT'nin bunun için yerleşik bir yanıtı vardır. Her cihaz bir status topic'inde last will mesajıyla bağlanır; örneğin <prefix>/sensors/kitchen/status topic'inde offline payload'u ve retain bayrağı ayarlı olarak, ve bağlandıktan hemen sonra retained bir online publish eder. Cihaz gücünü veya ağını kaybederse broker, will mesajını onun adına publish eder. Ayrıntılar MQTT last will and testament yazısındadır.

Uygulama tarafında, didConnectAck içine "\(topicPrefix)/sensors/+/status" için ikinci bir subscription ekleyin, değerleri bir [String: Bool] sözlüğünde saklayın ve sensörü çevrimdışı olan satırları gri gösterin. Her iki mesaj da retained olduğundan uygulama, bir sonraki okumayı beklemeden, bağlandığı anda her sensörün güncel durumunu öğrenir.

Aynı fikir uygulamanın kendisi için de geçerlidir. Diğer sistemlerin bir operatörün telefonunun bağlı olup olmadığını bilmesi gerekiyorsa, modelin initializer'ında, connect() çağrılmadan önce bir will ayarlayın:

mqtt.willMessage = CocoaMQTTMessage(topic: "\(self.topicPrefix)/apps/\(id)/status",
                                    string: "offline", qos: .qos1, retained: true)

Çoğu tüketici uygulaması için bu gereksizdir; ancak operatör ve saha servisi uygulamalarında süpervizörlere kimin bağlı olduğunu canlı olarak gösterir.

TLS Yerine WebSocket Kullanma

Bazı kurumsal ve otel Wi-Fi ağları 8883 portunu engeller. CocoaMQTT, MQTT'yi 8084 portunda sıradan HTTPS trafiği gibi görünen güvenli WebSocket üzerinden tünelleyebilir:

let websocket = CocoaMQTTWebSocket(uri: "/mqtt")
let client = CocoaMQTT(clientID: id, host: "broker.connectmq.com", port: 8084, socket: websocket)
client.enableSSL = true

Modeldeki diğer her şey aynı kalır. Ödünleşimler WebSocket üzerinden MQTT yazısında ele alınır.

Özel Bir Broker ile Üretime Geçiş

Prototip çalıştığında public broker'dan kendi barındırılan MQTT broker'ınıza geçin:

  1. ConnectMQ portalında bir broker oluşturun ve üretilen kullanıcı adını, parolayı ve client ID'yi kopyalayın.
  2. Modeli bunlarla başlatın: MQTTModel(username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientID: "YOUR_CLIENT_ID", topicPrefix: "acme/home1").
  3. Parolayı UserDefaults veya kaynak kodda değil, Keychain'de saklayın.
  4. 8883 portunda enableSSL = true ayarını koruyun.

ConnectMQ client ID'leri verilen kimlik bilgilerine bağladığı için, bir App Store uygulamasının kurulu her kopyasına tek bir paylaşılan oturum bilgisini gömmekten kaçının. Kullanıcı başına veya cihaz başına kimlik bilgisi veren bir backend daha güvenli bir tasarımdır: uygulama kullanıcıyı oturum açtırır, kendi MQTT kullanıcı adını, parolasını ve client ID'sini HTTPS üzerinden alır, bunları Keychain'de saklar; backend'iniz de diğer herkese uygulama güncellemesi göndermeden tek bir kullanıcının erişimini iptal edebilir. MQTT güvenliği en iyi uygulamaları rehberi kimlik bilgisi yönetimini daha derinlemesine ele alır.

Sorun Giderme Kontrol Listesi

Sorun Kontrol
didConnectAck hiç tetiklenmiyor 8883 portu için enableSSL açık, 1883 için kapalı mı? TLS/düz bağlantı uyuşmazlığı handshake'i kilitler.
Ack .identifierRejected Client ID boş veya başka bir oturum tarafından kullanılıyor.
Ack .badUsernameOrPassword Kimlik bilgileri sonda boşlukla kopyalanmış veya broker yanlış.
Simülatörde çalışıyor, cihazda çalışmıyor Cihaz 8883'ü engelleyen bir ağda; 8084'te WebSocket'i deneyin.
Telefonun kilidi açıldıktan sonra değerler duruyor scenePhase ile yeniden bağlanma eksik veya subscription'lar didConnectAck içinde geri yüklenmiyor.
SwiftUI'da mor çalışma zamanı uyarıları @Published özelliği main actor dışında güncelleniyor.

ConnectMQ ile Başlayın

SwiftUI uygulamanız üretime yalnızca bir kimlik bilgisi değişikliği uzaklıkta. app.connectmq.com üzerinde kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun, kullanıcı adını, parolayı ve client ID'yi MQTTModel içine yapıştırın ve iPhone'unuzla cihazlarınızın birbiriyle konuşmasını portalın canlı topic explorer'ı ile izleyin. Broker'ı ConnectMQ çalıştırır, siz de uygulamaya odaklanırsınız.

Sık Sorulan Sorular

iOS'ta hangi Swift MQTT kütüphanesini kullanmalıyım?

CocoaMQTT en yaygın kullanılan seçenektir; Swift Package Manager desteği, TLS, WebSocket ve hem MQTT 3.1.1 hem MQTT 5 client'ları sunar. Platformlar arası daha geniş bir karşılaştırma için MQTT client kütüphaneleri genel bakışına göz atın.

Bir iOS uygulaması arka planda MQTT mesajı alabilir mi?

Güvenilir şekilde hayır. iOS arka plandaki uygulamaları ve soketlerini askıya alır; bu yüzden uygulama aktif olduğunda yeniden bağlanın ve kilitli bir telefona ulaşması gereken uyarılar için push bildirimleri kullanın.

MQTT için App Transport Security istisnasına ihtiyacım var mı?

Herkesçe güvenilen bir sertifika kullanan bir broker'a TLS ile bağlandığınızda hayır; broker.connectmq.com da 8883 ve 8084 portlarında böyle bir sertifika kullanır. ATS kuralları URL yüklemeye uygulanır; zaten yayınlanan uygulamalarda 1883 üzerindeki düz TCP MQTT'den kaçınılmalıdır.

CocoaMQTT ile MQTT 5 nasıl kullanılır?

CocoaMQTT yerine CocoaMQTT5 sınıfını kullanın. API'si benzerdir ve MQTT 5 özelliklerini ekler; ConnectMQ her iki protokol sürümünü de destekler.

İlgili yazılar

Test, Betik ve Hata Ayıklama için En İyi MQTT CLI Araçları

mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, MQTT.js CLI, curl ve mqttui'nin uygulamalı karşılaştırması; betik yazma ve hata ayıklama için doğru aracı seçebilmeniz için aynı test broker.connectmq.com üzerinde çalıştırıldı.

7 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.