Tutorial 17: Multithreading with Grand Central Dispatch (GCD) and Async/Await in Swift
Introduction Multithreading is a key technique in iOS development for ensuring smooth user experiences. Swift provides two powerful tools for managing concurrent tasks: Grand Central Dispatch (GCD) and Async/Await. GCD is a low-level API for managing background tasks efficiently. Async/Await is a modern Swift concurrency feature that simplifies asynchronous programming with structured concurrency. In this tutorial, we will learn how to use these concepts by building a Voice Recorder app that runs in the background. Section 1: Understanding GCD and Async/Await What is Grand Central Dispatch (GCD)? GCD is Apple's technology for managing threads and task execution asynchronously. It provides: Dispatch Queues (Serial & Concurrent) Main and Background Queues Work Items (sync vs async execution) Example: Running tasks with GCD DispatchQueue.global(qos: .background).async { print("This is running in the background") } What is Async/Await? Async/Await simplifies writing asynchronous code. It allows developers to work with concurrency in a more readable manner. Example: Using Async/Await in Swift func fetchData() async throws -> String { let result = try await someAsyncFunction() return result } Section 2: Setting Up the Voice Recorder App Creating the Xcode Project Open Xcode and create a new iOS project. Select App, choose SwiftUI as the interface, and name it "VoiceRecorder". Enable background mode for Audio in the project settings. Section 3: Implementing Audio Recording with AVAudioRecorder Import AVFoundation import AVFoundation Request Microphone Permissions class AudioRecorder: NSObject, ObservableObject { private var audioRecorder: AVAudioRecorder? private var recordingSession: AVAudioSession = AVAudioSession.sharedInstance() override init() { super.init() requestPermissions() } private func requestPermissions() { recordingSession.requestRecordPermission { allowed in DispatchQueue.main.async { if allowed { print("Microphone access granted") } else { print("Microphone access denied") } } } } } Section 4: Recording and Saving Audio Start and Stop Recording func startRecording() { let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a") let settings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] do { audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings) audioRecorder?.record() print("Recording started") } catch { print("Recording failed: \(error.localizedDescription)") } } func stopRecording() { audioRecorder?.stop() print("Recording stopped") } Helper Function to Get Directory func getDocumentsDirectory() -> URL { FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] } Section 5: Running Tasks in the Background Enable Background Audio Open Signing & Capabilities. Add Background Modes and enable Audio, AirPlay, and Picture in Picture. Keep the Recording Running in the Background func configureAudioSession() { do { try recordingSession.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker) try recordingSession.setActive(true) } catch { print("Failed to configure audio session: \(error.localizedDescription)") } } Section 6: Integrating Async/Await for Smooth UI Using Async/Await for File Saving func saveRecording() async { let audioFile = getDocumentsDirectory().appendingPathComponent("recording.m4a") do { try await Task.sleep(nanoseconds: 2_000_000_000) // Simulate background work print("File saved at: \(audioFile)") } catch { print("Error saving file: \(error.localizedDescription)") } } Handling UI Updates in the Main Thread DispatchQueue.main.async { self.isRecording = false } Section 7: Building the SwiftUI Interface Designing the UI import SwiftUI struct ContentView: View { @StateObject private var audioRecorder = AudioRecorder() var body: some View { VStack { Button(action: { audioRecorder.startRecording() }) { Text("Start Recording") .padding() .background(Color.red) .foregroundColor(.white) .clipShape(Capsule()) } Button(action: {

Introduction
Multithreading is a key technique in iOS development for ensuring smooth user experiences. Swift provides two powerful tools for managing concurrent tasks: Grand Central Dispatch (GCD) and Async/Await.
- GCD is a low-level API for managing background tasks efficiently.
- Async/Await is a modern Swift concurrency feature that simplifies asynchronous programming with structured concurrency.
In this tutorial, we will learn how to use these concepts by building a Voice Recorder app that runs in the background.
Section 1: Understanding GCD and Async/Await
What is Grand Central Dispatch (GCD)?
GCD is Apple's technology for managing threads and task execution asynchronously. It provides:
- Dispatch Queues (Serial & Concurrent)
- Main and Background Queues
- Work Items (sync vs async execution)
Example: Running tasks with GCD
DispatchQueue.global(qos: .background).async {
print("This is running in the background")
}
What is Async/Await?
Async/Await simplifies writing asynchronous code. It allows developers to work with concurrency in a more readable manner.
Example: Using Async/Await in Swift
func fetchData() async throws -> String {
let result = try await someAsyncFunction()
return result
}
Section 2: Setting Up the Voice Recorder App
Creating the Xcode Project
- Open Xcode and create a new iOS project.
- Select App, choose SwiftUI as the interface, and name it "VoiceRecorder".
- Enable background mode for Audio in the project settings.
Section 3: Implementing Audio Recording with AVAudioRecorder
Import AVFoundation
import AVFoundation
Request Microphone Permissions
class AudioRecorder: NSObject, ObservableObject {
private var audioRecorder: AVAudioRecorder?
private var recordingSession: AVAudioSession = AVAudioSession.sharedInstance()
override init() {
super.init()
requestPermissions()
}
private func requestPermissions() {
recordingSession.requestRecordPermission { allowed in
DispatchQueue.main.async {
if allowed {
print("Microphone access granted")
} else {
print("Microphone access denied")
}
}
}
}
}
Section 4: Recording and Saving Audio
Start and Stop Recording
func startRecording() {
let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a")
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder?.record()
print("Recording started")
} catch {
print("Recording failed: \(error.localizedDescription)")
}
}
func stopRecording() {
audioRecorder?.stop()
print("Recording stopped")
}
Helper Function to Get Directory
func getDocumentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
Section 5: Running Tasks in the Background
Enable Background Audio
- Open Signing & Capabilities.
- Add Background Modes and enable Audio, AirPlay, and Picture in Picture.
Keep the Recording Running in the Background
func configureAudioSession() {
do {
try recordingSession.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker)
try recordingSession.setActive(true)
} catch {
print("Failed to configure audio session: \(error.localizedDescription)")
}
}
Section 6: Integrating Async/Await for Smooth UI
Using Async/Await for File Saving
func saveRecording() async {
let audioFile = getDocumentsDirectory().appendingPathComponent("recording.m4a")
do {
try await Task.sleep(nanoseconds: 2_000_000_000) // Simulate background work
print("File saved at: \(audioFile)")
} catch {
print("Error saving file: \(error.localizedDescription)")
}
}
Handling UI Updates in the Main Thread
DispatchQueue.main.async {
self.isRecording = false
}
Section 7: Building the SwiftUI Interface
Designing the UI
import SwiftUI
struct ContentView: View {
@StateObject private var audioRecorder = AudioRecorder()
var body: some View {
VStack {
Button(action: {
audioRecorder.startRecording()
}) {
Text("Start Recording")
.padding()
.background(Color.red)
.foregroundColor(.white)
.clipShape(Capsule())
}
Button(action: {
audioRecorder.stopRecording()
}) {
Text("Stop Recording")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
}
}
}
Section 8: Testing and Debugging
Running the App on a Real Device
- Use a real device for testing as the simulator does not support microphone input.
- Check logs using
print()
statements.
Debugging Common Issues
Issue | Solution |
---|---|
No microphone permission | Ensure NSMicrophoneUsageDescription is in Info.plist |
No audio recorded | Check audio session settings and file permissions |
App stops in background | Enable background audio mode |
Conclusion
In this tutorial, we learned:
- Basics of GCD and Async/Await
- How to record audio in iOS
- Running tasks in the background using GCD
- Using Async/Await for structured concurrency
- Building a simple SwiftUI Voice Recorder App
This knowledge enables you to build feature-rich apps that handle concurrency efficiently in Swift.
Next Steps
- Add UI feedback (e.g., waveforms, timers)
- Implement file playback functionality
- Upload recordings to the cloud using URLSession and Async/Await
Happy coding!