Swift provides DispatchQueue as an excellent layer above raw threads. But sometimes you want to create a new thread dedicated to some specific task. Or maybe implement your own concurrent executor. Swift gives you access to raw threads and in this article, I'll show how to use it.
Thread
Creating a thread in Swift is pretty simple using Thread
class. You can either specify objc
function through a selector as a starting point, or pass a closure, and, more convenient way, subclass Thread
.
class MyThread: Thread {
override func main() { // Thread's starting point
print("Hi from thread")
}
}
let thread = MyThread()
thread.start()
The thread is not started when the initializer is called. You need to call start()
method explicitly to start the thread.
The thread runs despite its handle returned by Thread
initializer. That's it — the variable can no longer exist and the thread will still run. That's fine, but you will lose the ability to control the thread: check if it's completed, wait for its completion, cancel it, etc.
Wait for completion, join a thread
Swift does not provide a way to wait for the thread's completion.
To wait for thread completion, we can join threads using DispatchGroup
class MyThread: Thread {
let waiter = DispatchGroup()
override func start() {
waiter.enter()
super.start()
}
override func main() {
task()
waiter.leave()
}
func task() {
print("Hi from thread")
}
func join() {
waiter.wait()
}
}
let thread = MyThread()
thread.start()
thread.join() // Waits for thread completion
Terminate the thread
The thread terminates automatically after reaching main
's end. To exit the thread in advance, you can call Thread.exit()
function from the thread. To use it correctly with created DispatchGroup
, it's better to create a custom exit method:
class MyThread: Thread {
...
func exit() {
waiter.leave()
Thread.exit()
}
...
}
Cancel the thread
Apart from terminating the thread, you can cancel it, by calling cancel()
method on the thread's handle or inside the thread itself. This sets isCancelled
property to true
.
For this feature to work, in the thread you need to check this flag periodically and then call exit if the flag is true
.
In the example below, we can use cancel to notify the thread about a timeout.
class MyThread: Thread {
let waiter = DispatchGroup()
override func start() {
waiter.enter()
super.start()
}
override func main() {
task()
waiter.leave()
}
func exit() {
waiter.leave()
Thread.exit()
}
func task() {
let start = Date.now
for _ in 0...100500 {
if isCancelled {
let seconds = Double(Date.now.timeIntervalSince(start))
print("Cancelled after \(seconds) seconds")
exit()
}
Thread.sleep(forTimeInterval: 0.01) // Long task
}
}
func join() {
waiter.wait()
}
}
let thread = MyThread()
DispatchQueue.global().asyncAfter(
deadline: .now().advanced(by: .seconds(5))) {
thread.cancel()
}
thread.start()
thread.join()
Output: Cancelled after 5.509860992431641 seconds
Concurrency must be safe
Mind that in case of using several threads, shared data and structures must be thread-safe.
I recently released an article on Actors model in Swift. Actors is an architectural approach to concurrency

Use cases
- A long-running task in your app
If there is a long-running task in your app, then consider creating a dedicated thread for it.
- Creating concurrent executors
Swift's DispatchQueue and OperationQueue are powerful tools, but even their functions are limited.
For example, there is no strand executor or explicit thread pool.
- Writing your own Thread Pool as a practice and a pet-project
Why not? The best way to understand how DispatchQueue works is to write your own!
Finally
Check out my quick guide to async/await in Swift to get a better grip on concurrency in Swift.

Also, the iOS section of my blog has cool staff about iOS and Swift development. Check it out!
