dispatch_async
async request
concurrency
programming issue
debugging

issue with dispatch_async and async request

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

dispatch_async and asynchronous requests are related but not interchangeable tools. In iOS, network APIs already run asynchronously, while GCD queues decide where your own closure code executes. Most bugs happen when these roles are mixed without a clear thread-ownership plan.

dispatch_async Versus Async Network APIs

DispatchQueue.async schedules work and returns immediately. URLSession performs I/O in the background and triggers completion later. Wrapping every request inside another global queue call usually adds complexity without benefit.

swift
1import Foundation
2
3func fetchJSON(url: URL, completion: @escaping (Result<[String: Any], Error>) -> Void) {
4    let task = URLSession.shared.dataTask(with: url) { data, _, error in
5        if let error = error {
6            completion(.failure(error))
7            return
8        }
9
10        do {
11            let data = data ?? Data()
12            let json = try JSONSerialization.jsonObject(with: data, options: [])
13            guard let dict = json as? [String: Any] else {
14                throw NSError(domain: "Decode", code: 1)
15            }
16            completion(.success(dict))
17        } catch {
18            completion(.failure(error))
19        }
20    }
21    task.resume()
22}

This function is non-blocking without any explicit background dispatch.

Safe Main-Queue Handoff for UI Mutations

Completion handlers may run off the main thread. UI changes must run on the main queue.

swift
1import UIKit
2
3final class SettingsViewController: UIViewController {
4    @IBOutlet private weak var statusLabel: UILabel!
5
6    override func viewDidAppear(_ animated: Bool) {
7        super.viewDidAppear(animated)
8        loadSettings()
9    }
10
11    private func loadSettings() {
12        let url = URL(string: "https://example.com/api/settings")!
13
14        fetchJSON(url: url) { [weak self] result in
15            switch result {
16            case .success(let payload):
17                let count = payload.keys.count
18                DispatchQueue.main.async {
19                    self?.statusLabel.text = "Loaded \(count) settings"
20                }
21            case .failure(let error):
22                DispatchQueue.main.async {
23                    self?.statusLabel.text = "Failed: \(error.localizedDescription)"
24                }
25            }
26        }
27    }
28}

Keep one explicit UI handoff near the mutation site. Multiple nested queue hops make ordering hard to debug.

Modern Swift Concurrency Pattern

If your deployment target allows it, async and await simplifies error and cancellation flow.

swift
1import Foundation
2
3struct SettingsService {
4    func fetchSettings() async throws -> [String: Any] {
5        let url = URL(string: "https://example.com/api/settings")!
6        let (data, _) = try await URLSession.shared.data(from: url)
7        let obj = try JSONSerialization.jsonObject(with: data, options: [])
8        guard let dict = obj as? [String: Any] else {
9            throw NSError(domain: "Decode", code: 2)
10        }
11        return dict
12    }
13}
14
15@MainActor
16func refreshScreen(service: SettingsService) async {
17    do {
18        let settings = try await service.fetchSettings()
19        print("settings keys:", settings.keys.sorted())
20    } catch {
21        print("refresh failed:", error)
22    }
23}

@MainActor removes manual dispatch calls and makes thread expectations explicit.

Debug Queue and Thread Behavior

When timing bugs appear, log queue labels and thread context in strategic points.

swift
1import Foundation
2
3func debugContext(_ marker: String) {
4    let queue = String(cString: __dispatch_queue_get_label(nil), encoding: .utf8) ?? "unknown"
5    print("[\(marker)] queue=\(queue) main=\(Thread.isMainThread)")
6}
7
8DispatchQueue.global(qos: .utility).async {
9    debugContext("background start")
10    DispatchQueue.main.async {
11        debugContext("main update")
12    }
13}

This lightweight tracing catches accidental main-thread networking and hidden background UI mutations.

Testing Concurrency in Practice

To validate queue behavior, run your screen on a slow network profile and interact quickly, such as navigating away during active requests. Confirm that canceled views do not receive UI updates and that callback logic tolerates out-of-order responses. In unit tests, inject a mock service that delays completion and returns deterministic payloads. This makes race conditions reproducible. For integration tests, monitor the main thread checker and runtime warnings for UIKit updates on background threads. Concurrency bugs often hide in happy-path manual testing, so include cancellation and repeated refresh actions in your test checklist.

Common Pitfalls

A common mistake is dispatching network starts to global queues even though the API is already asynchronous. This obscures execution flow.

Another issue is updating UIKit views from background callbacks. It may appear fine on fast devices, then fail under stress.

Retain cycles are also common in long-running requests. Capture self weakly where view lifecycle should break references.

Finally, cancellation is often ignored. If a user leaves the screen, orphaned requests can still update stale state unless you cancel or gate responses.

Summary

  • URLSession is asynchronous by design, so extra background dispatch is often unnecessary.
  • Use DispatchQueue.main.async only at UI mutation points.
  • Prefer async and await plus @MainActor for clearer concurrency boundaries.
  • Add queue-context logs when diagnosing race conditions.
  • Handle cancellation and object lifetimes explicitly.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.