Warum Hintergrund-Threads der Schmerz eines jeden iOS-Entwicklers sind?
\n\nStellen Sie sich vor: Sie schreiben eine App zum Herunterladen von Fotos aus dem Internet. Alles funktioniert perfekt... bis der Benutzer auf die Schaltfläche "Herunterladen" klickt. Der Bildschirm friert ein, die App "ruckelt", und nach ein paar Sekunden beendet iOS einfach Ihren Prozess. Kommt Ihnen das bekannt vor?
\n\nDas Problem ist, dass alle Operationen standardmäßig auf dem Hauptthread (main thread) ausgeführt werden. Der Hauptthread ist aber für die Darstellung der UI zuständig – wenn Sie ihn mit einer langen Aufgabe blockieren, reagiert die Oberfläche nicht mehr auf Berührungen. Die Lösung? Verwenden Sie einen Background-Thread (Hintergrundthread) in Swift.
\n\nIn diesem Artikel werden wir anhand praktischer Beispiele durchgehen, wie man mit Hintergrund-Threads arbeitet. Sie erfahren mehr über GCD, OperationQueue und das neue async/await aus Swift 5.5+. Los geht's!
\n\nWas ist ein Thread in Swift?
\n\nEin Thread ist ein leichter Prozess, der den Code eines Programms ausführt. Eine iOS-App hat einen Hauptthread (main thread) und viele Hintergrund-Threads. Der Hauptthread kümmert sich um die UI: Er aktualisiert den Bildschirm, verarbeitet Gesten und Animationen. Alles, was die Oberfläche betrifft, sollte nur auf dem Hauptthread stattfinden.
\n\nHintergrund-Threads (background threads) werden benötigt für:
\n- \n
- Herunterladen von Daten aus dem Netzwerk \n
- Arbeiten mit Dateien (Lesen/Schreiben) \n
- Komplexe Berechnungen (Bildverarbeitung, Verschlüsselung) \n
- Arbeiten mit Datenbanken \n
Wenn Sie versuchen, die UI von einem Hintergrund-Thread aus zu aktualisieren, stürzt die App entweder mit einem Fehler ab, oder Sie sehen ein seltsames Verhalten der Oberfläche.
\n\nGCD (Grand Central Dispatch) – die klassische Methode
\n\nGrand Central Dispatch (GCD) ist eine Low-Level-Bibliothek zur Thread-Verwaltung, die in iOS/macOS integriert ist. Sie arbeitet über Warteschlangen (queues): Sie legen eine Aufgabe in die Warteschlange, und das System entscheidet selbst, auf welchem Thread sie ausgeführt wird.
\n\nDispatchQueue.main – der Hauptthread
\nWenn Sie Code auf dem Hauptthread ausführen müssen (z. B. ein UILabel aktualisieren), verwenden Sie:
\n\nDispatchQueue.main.async {\n self.label.text = "Download abgeschlossen!"\n}\n\nDispatchQueue.global() – der Hintergrund-Thread
\nFür Hintergrundaufgaben verwenden Sie die globale Warteschlange:
\n\nDispatchQueue.global(qos: .background).async {\n // Schwere Arbeit\n let imageData = try? Data(contentsOf: url)\n \n // Zurück zum Hauptthread für die UI\n DispatchQueue.main.async {\n self.imageView.image = UIImage(data: imageData!)\n }\n}\n\nDer Parameter qos (Quality of Service) bestimmt die Priorität der Aufgabe:
- \n
.userInteractive– die höchste Priorität, für UI \n .userInitiated– der Benutzer wartet auf das Ergebnis \n .utility– langwierige Aufgaben (Dateidownloads) \n .background– niedrige Priorität (Synchronisation, Backups) \n
OperationQueue – mehr Kontrolle
\n\nOperationQueue ist ein Aufsatz auf GCD mit zusätzlichen Funktionen:
\n- \n
- Abbrechen von Aufgaben \n
- Abhängigkeiten zwischen Aufgaben \n
- Maximale Anzahl paralleler Threads \n
Beispiel für das Herunterladen mehrerer Bilder:
\n\nlet queue = OperationQueue()\nqueue.maxConcurrentOperationCount = 2 // Nicht mehr als 2 Aufgaben gleichzeitig\n\nlet operation1 = BlockOperation {\n // Erstes Bild herunterladen\n sleep(2)\n print("Foto 1 heruntergeladen")\n}\n\nlet operation2 = BlockOperation {\n // Zweites Bild herunterladen\n sleep(3)\n print("Foto 2 heruntergeladen")\n}\n\n// operation2 beginnt erst nach Abschluss von operation1\noperation2.addDependency(operation1)\n\nqueue.addOperations([operation1, operation2], waitUntilFinished: false)\n\nDies ist praktisch, wenn Aufgaben in einer strengen Reihenfolge oder mit Ressourcenbeschränkungen ausgeführt werden müssen.
\n\nModernes async/await (Swift 5.5+)
\n\nSeit Swift 5.5 bietet Ap