Einleitung: Warum friert Ihr iPhone nicht ein, während Sie durch den Feed scrollen?
\n\nHaben Sie sich jemals gefragt, warum moderne iOS-Apps nicht ruckeln, selbst wenn sie gleichzeitig Dutzende von Bildern laden, Tastendrücke verarbeiten und Daten in die Datenbank schreiben? Die Antwort ist einfach – Parallelität. In der Welt von Swift (und nicht nur dort) ist dies eines der heißesten Themen. Und wenn Sie in Swift programmieren, aber immer noch bei Threads, Goroutinen und dem modernen async/await durcheinanderkommen – willkommen im Club. Heute ordnen wir alles, damit Ihr Code fliegt und nicht kriecht.
In diesem Artikel werden wir nicht nur trockene Definitionen auflisten. Wir werden uns reale Beispiele ansehen, Ansätze vergleichen und die Hauptfrage beantworten: Was soll ich für mein Projekt wählen – das gute alte GCD oder das neue Swift Concurrency?
\n\n\n\nThreads: Das Fundament, auf dem alles ruht
\n\nBeginnen wir mit der Basis. Ein Thread ist die kleinste Verarbeitungseinheit, die ein Betriebssystem ausführen kann. Stellen Sie sich ein Fließband vor: Ein Thread ist eine Linie, auf der Aufgaben bewegt werden. Wenn Sie einen Einkernprozessor haben, werden die Threads nacheinander ausgeführt, aber sehr schnell umgeschaltet, wodurch die Illusion von Parallelität entsteht. Auf Mehrkernprozessoren können Threads tatsächlich parallel ausgeführt werden.
\n\nIn Swift war lange Zeit Grand Central Dispatch (GCD) das wichtigste Werkzeug für die Arbeit mit Threads. Sie erstellten einfach eine Warteschlange und sendeten einen Closure hinein:
\n\nDispatchQueue.global(qos: .background).async {\n // Schwere Arbeit im Hintergrund\n let result = heavyComputation()\n DispatchQueue.main.async {\n // UI-Update im Hauptthread\n self.label.text = result\n }\n}\n\nGanz einfach, oder? Aber GCD hat seine Tücken:
\n- \n
- Sie sind selbst für die Rückkehr zum Hauptthread verantwortlich (leicht zu vergessen und führt zu einem Crash). \n
- Es gibt keine integrierte Unterstützung für das Abbrechen von Aufgaben (Sie müssen Flags manuell schreiben). \n
- Es ist schwierig, Ketten von abhängigen Operationen ohne Verschachtelung von Closures zu verarbeiten (der sogenannte „Callback Hell“). \n
Trotzdem ist GCD immer noch lebendig und wird in Legacy-Projekten verwendet. Aber modernes Swift bietet etwas weitaus Eleganteres.
\n\nGoroutinen in Swift? Nein, das ist nicht Go
\n\nWenn man von Goroutinen spricht, denkt man normalerweise an Go. In Swift gibt es kein direktes Äquivalent zu Goroutinen, aber es gibt leichtgewichtige Aufgaben (Tasks), die sich sehr ähnlich verhalten. Sie sind nicht an einen bestimmten Thread gebunden – das System entscheidet selbst, in welchem Thread die Aufgabe ausgeführt wird, und kann sie zwischen Threads umschalten, ohne für jeden Aufruf neue zu erstellen.
\n\nIm Gegensatz zu Threads, die jeweils etwa 8 KB Stack „wiegen“, wiegen Swift Concurrency Tasks praktisch nichts. Sie können Tausende von Aufgaben starten, ohne das Risiko eines Speicherüberlaufs. Das ist die „Parallelität ohne Schmerz“.
\n\nSehen Sie, wie das aussieht:
\n\nTask {\n let image = await downloadImage(from: url)\n await MainActor.run {\n self.imageView.image = image\n }\n}\n\nBemerkt? Kein DispatchQueue.main.async. Einfach await MainActor.run. Sauber, verständlich, sicher.
async/await: Die Revolution, die stattfand
\n\nMit der Veröffentlichung von Swift 5.5 kam die integrierte Unterstützung für async/await. Das ist nicht nur syntaktischer Zucker – es ist ein Paradigmenwechsel. Jetzt können Sie asynchronen Code so schreiben, als wäre er synchron, ohne dabei den Thread zu blockieren.
\n\nEine asynchrone Funktion zu deklarieren ist einfach:
\n\nfunc fetchUserData() async throws -> User {\n let url = URL(string: "https://api.example.com/user")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return try JSONDecoder().decode(User.self, from: data)\n}\n\nUnd sie aufzurufen ist noch einfacher:
\n\nTask {\n do {\n let user = try await fetchUserData()\n print("User nam