Main Thread (Waiter): Imagine a waiter serving customers at a restaurant. They interact directly with customers, take orders, deliver food, and handle payments. They must remain responsive and attentive at all times—just like the main thread, which updates the user interface and handles user interactions. If the waiter stops or slows down, customers become frustrated.
Background Thread (Kitchen Staff): The kitchen staff prepares the food. They work behind the scenes, independently preparing orders, washing dishes, and organizing supplies. Their tasks might be time-consuming, but they do not directly interact with customers. These are like background threads, handling tasks that could take time or block the main thread without directly affecting the immediate responsiveness.
Grand Central Dispatch (GCD) and Operation Queues, because they are easier to manage and have a smaller overhead.
1
2
3
DispatchQueue.main.async {
completionHandler(pokemonData)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import PlaygroundSupport
import UIKit
// Necessary to keep the playground running
PlaygroundPage.current.needsIndefiniteExecution = true
// Simulate a heavy task on a background queue
DispatchQueue.global(qos: .background).async {
print("🚀 Heavy task started on background thread.")
// Simulate heavy computation or network task
for i in 1...5 {
print("Processing item \(i)")
sleep(1) // Simulate a time-consuming task
}
print("✅ Heavy task completed!")
// Update UI or execute main-thread task after background work
DispatchQueue.main.async {
print("🎉 Updating UI on main thread.")
// e.g., update labels, images, etc.
}
}
print("⏳ Main thread continues executing without waiting...")
Pyramid of Doom, occurs when asynchronous operations are nested deeply within callbacks, making code increasingly unreadable, difficult to debug, and maintain.import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
func fetchUser(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion("User123")
}
}
func fetchOrders(for user: String, completion: @escaping ([String]) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion(["Order1", "Order2", "Order3"])
}
}
func fetchOrderDetails(order: String, completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion("Details for \(order)")
}
}
// Callback Hell Example
fetchUser { user in
print("User fetched: \(user)")
fetchOrders(for: user) { orders in
print("Orders fetched: \(orders)")
fetchOrderDetails(order: orders[0]) { details in
print("First order details fetched: \(details)")
fetchOrderDetails(order: orders[1]) { details in
print("Second order details fetched: \(details)")
fetchOrderDetails(order: orders[2]) { details in
print("Third order details fetched: \(details)")
DispatchQueue.main.async {
print("✅ Finally done updating UI.")
}
}
}
}
}
}
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// Async functions using Swift concurrency
func fetchUser() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "User123"
}
func fetchOrders(for user: String) async -> [String] {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return ["Order1", "Order2", "Order3"]
}
func fetchOrderDetails(order: String) async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Details for \(order)"
}
Task {
let user = await fetchUser()
print("User fetched: \(user)")
let orders = await fetchOrders(for: user)
print("Orders fetched: \(orders)")
for order in orders {
let details = await fetchOrderDetails(order: order)
print("Order details fetched: \(details)")
}
DispatchQueue.main.async {
print("✅ Finally done updating UI.")
}
}