Background thread in Swift: a guide for beginners and pros

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

Why background threads are a pain for every iOS developer?

\n\n

Imagine: you are writing an app to download photos from the internet. Everything works perfectly... until the user presses the "Download" button. The screen freezes, the app lags, and after a couple of seconds, iOS simply kills your process. Sound familiar?

\n\n

The problem is that all operations run on the main thread by default. And the main thread is responsible for rendering the UI — if you block it with a long task, the interface will stop responding to touches. The solution? Use a background thread in Swift.

\n\n

In this article, we will figure out how to work with background threads using real examples. You will learn about GCD, OperationQueue, and the new async/await from Swift 5.5+. Let's go!

\n\n

What is a thread in Swift?

\n\n

A thread is a lightweight process that executes program code. An iOS app has a main thread and many background threads. The main thread handles the UI: updates the screen, processes gestures, animations. Everything related to the interface must happen only on the main thread.

\n\n

Background threads are needed for:

\n
    \n
  • Downloading data from the network
  • \n
  • Working with files (reading/writing)
  • \n
  • Complex computations (image processing, encryption)
  • \n
  • Working with databases
  • \n
\n\n

If you try to update the UI from a background thread, the app will crash with an error, or you will see strange interface behavior.

\n\n

GCD (Grand Central Dispatch) — the classic way

\n\n

Grand Central Dispatch (GCD) is a low-level library for thread management built into iOS/macOS. It works through queues: you put a task in a queue, and the system itself decides which thread to execute it on.

\n\n

DispatchQueue.main — the main thread

\n

If you need to execute code on the main thread (for example, to update a UILabel), use:

\n\n
DispatchQueue.main.async {\n    self.label.text = "Loading complete!"\n}
\n\n

DispatchQueue.global() — background thread

\n

For background tasks, use the global queue:

\n\n
DispatchQueue.global(qos: .background).async {\n    // Heavy work\n    let imageData = try? Data(contentsOf: url)\n    \n    // Return to the main thread for UI\n    DispatchQueue.main.async {\n        self.imageView.image = UIImage(data: imageData!)\n    }\n}
\n\n

The qos (Quality of Service) parameter determines the priority of the task:

\n
    \n
  • .userInteractive — highest priority, for UI
  • \n
  • .userInitiated — user is waiting for the result
  • \n
  • .utility — long tasks (file downloads)
  • \n
  • .background — low priority (synchronization, backups)
  • \n
\n\n

OperationQueue — more control

\n\n

OperationQueue is an abstraction over GCD with additional capabilities:

\n
    \n
  • Cancelling tasks
  • \n
  • Dependencies between tasks
  • \n
  • Maximum number of concurrent threads
  • \n
\n\n

Example of downloading multiple images:

\n\n
let queue = OperationQueue()\nqueue.maxConcurrentOperationCount = 2 // No more than 2 tasks at a time\n\nlet operation1 = BlockOperation {\n    // Downloading the first image\n    sleep(2)\n    print("Photo 1 downloaded")\n}\n\nlet operation2 = BlockOperation {\n    // Downloading the second image\n    sleep(3)\n    print("Photo 2 downloaded")\n}\n\n// operation2 will start only after operation1 completes\noperation2.addDependency(operation1)\n\nqueue.addOperations([operation1, operation2], waitUntilFinished: false)
\n\n

This is convenient when you need to execute tasks in a strict order or with resource constraints.

\n\n

Modern async/await (Swift 5.5+)

\n\n

Starting with Swift 5.5, Ap

Blogs

Book Recommendations