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:
- 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şiniTask { @MainActor in ... }içine sarar. didConnectAckiçinde subscribe olmak.cleanSession = trueolduğ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.- 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:
- ConnectMQ portalında bir broker oluşturun ve üretilen kullanıcı adını, parolayı ve client ID'yi kopyalayın.
- Modeli bunlarla başlatın:
MQTTModel(username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientID: "YOUR_CLIENT_ID", topicPrefix: "acme/home1"). - Parolayı
UserDefaultsveya kaynak kodda değil, Keychain'de saklayın. - 8883 portunda
enableSSL = trueayarı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.



