Concurrency in Swift: Threads, Goroutines, and async/await — How Not to Go Crazy

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why doesn't your iPhone freeze while you scroll through your feed?

\n\n

Have you ever wondered why modern iOS apps don't lag, even when they are simultaneously loading dozens of images, processing taps, and writing data to the database? The answer is simple — concurrency. In the world of Swift (and beyond), this is one of the hottest topics. And if you write in Swift, but still get confused by threads, goroutines, and the trendy async/await — welcome to the club. Today we'll break everything down so your code flies, not crawls.

\n\n

In this article, we won't just list dry definitions. We'll look at real examples, compare approaches, and answer the main question: what to choose for your project — the good old GCD or the new Swift Concurrency?

\n\n\n\n

Threads: The Foundation Everything Rests On

\n\n

Let's start with the basics. A thread is the smallest unit of processing that an operating system can execute. Imagine a conveyor belt: one thread is one line along which tasks move. If you have a single-core processor, threads will execute one after another, but switching very quickly, creating the illusion of parallelism. On multi-core processors, threads can actually execute in parallel.

\n\n

In Swift, for a long time, the main tool for working with threads was Grand Central Dispatch (GCD). You would simply create a queue and dispatch a closure to it:

\n\n
DispatchQueue.global(qos: .background).async {\n    // Heavy work in the background\n    let result = heavyComputation()\n    DispatchQueue.main.async {\n        // Update UI on the main thread\n        self.label.text = result\n    }\n}
\n\n

Simple, right? But GCD has its nuances:

\n
    \n
  • You are responsible for returning to the main thread (easy to forget and get a crash).
  • \n
  • No built-in support for task cancellation (you have to write flags manually).
  • \n
  • Difficult to handle chains of dependent operations without nesting closures (the so-called "callback hell").
  • \n
\n\n

Nevertheless, GCD is still alive and used in legacy projects. But modern Swift offers something much more elegant.

\n\n

Goroutines in Swift? No, This Isn't Go

\n\n

When people talk about goroutines, they usually think of Go. Swift doesn't have a direct analog of goroutines, but it has lightweight tasks that behave very similarly. They are not tied to a specific thread — the system itself decides which thread to execute a task on and can switch them between threads without creating new ones for each call.

\n\n

Unlike threads, which "weigh" about 8 KB of stack each, Swift Concurrency tasks weigh practically nothing. You can launch thousands of tasks without the risk of overflowing memory. This is the very "concurrency without pain".

\n\n

See how it looks:

\n\n
Task {\n    let image = await downloadImage(from: url)\n    await MainActor.run {\n        self.imageView.image = image\n    }\n}
\n\n

Notice? No DispatchQueue.main.async. Just await MainActor.run. Clean, clear, safe.

\n\n

async/await: The Revolution That Happened

\n\n

With the release of Swift 5.5, built-in support for async/await arrived. This is not just syntactic sugar — it's a paradigm shift. Now you can write asynchronous code as if it were synchronous, but without blocking the thread.

\n\n

Declaring an async function is simple:

\n\n
func 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\n

And calling it is even simpler:

\n\n
Task {\n    do {\n        let user = try await fetchUserData()\n        print("User nam                

Blogs

Book Recommendations