Singleton design patterns in Swift

Design patterns are essential in software development as they provide proven solutions to common problems, making code more reusable, maintainable, and scalable. In Swift, these patterns are particularly beneficial in iOS development due to the complexity of mobile applications.

Let’s dive deep into the Singleton Pattern in detail.

class ThreadSafeSingleton {
         // Initialization with a Closure
    static let shared: ThreadSafeSingleton = {
        let instance = ThreadSafeSingleton()
        // Perform additional setup if needed
        // e.g., instance.configure()
        return instance
    }()

    private init() {
        // Private initialization to ensure just one instance is created.
    }

    func someMethod() {
        // Method implementation
    }
}

class ThreadSafeSingleton {
    // Direct Initialization
    static let shared = ThreadSafeSingleton()

    private init() {
        // Private initialization to ensure just one instance is created.
    }

    func someMethod() {
        // Method implementation
    }
}

When a Singleton class has properties that can be read or modified by multiple threads, or methods that alter the internal state, ensuring thread safety is crucial. This can be achieved using various synchronization techniques such as

  1. Serial Dispatch Queues
  2. Actors (Swift Concurrency)
  3. Concurrent Queue and Barrier Flag
  4. Locks


  1. Serial Dispatch Queues to achieve thread safety in singleton pattern

Serial dispatch queues ensure that tasks are executed one at a time, making them a great tool for ensuring thread safety.

import Foundation

class ThreadSafeSingleton {
    static let shared = ThreadSafeSingleton()
    private var _value: Int = 0
    private let queue = DispatchQueue(label: "com.example.ThreadSafeSingletonQueue")

    private init() {
        // Private initialization to ensure just one instance is created.
    }

    var value: Int {
        get {
            return queue.sync {
                _value
            }
        }
        set {
            queue.sync {
                self._value = newValue
            }
        }
    }

    func someMethod() {
        queue.sync {
            // Modify internal state
        }
    }
}

2. Actors (Swift Concurrency)

Actors provide a way to manage state in a thread-safe manner through isolated state management.

import Foundation

actor ThreadSafeSingleton {
    static let shared = ThreadSafeSingleton()
    var value: Int = 0

    private init() {
        // Private initialization to ensure just one instance is created.
    }

    func someMethod() {
        // Modify internal state
    }
}

// Usage Example (Async context required)
Task {
    let singleton = await ThreadSafeSingleton.shared
    await singleton.someMethod()
    print(await singleton.value)
    await singleton.value = 42
}

 3. Concurrent Queue and Barrier Flag

This ensures that read operations can be performed concurrently while write operations are performed exclusively, preventing race conditions

import Foundation

class ThreadSafeSingleton {
    static let shared = ThreadSafeSingleton()
    private var _value: Int = 0
    private let queue = DispatchQueue(label: "com.example.ThreadSafeSingletonQueue", attributes: .concurrent)

    private init() {
        // Private initialization to ensure just one instance is created.
    }

    var value: Int {
        get {
            return queue.sync {
                _value
            }
        }
        set {
            queue.async(flags: .barrier) {
                self._value = newValue
            }
        }
    }

    func someMethod() {
        queue.async(flags: .barrier) {
            // Modify internal state
        }
    }
}

// Usage Example
let singleton = ThreadSafeSingleton.shared

DispatchQueue.global().async {
    singleton.value = 42
}

DispatchQueue.global().async {
    print(singleton.value)
}

DispatchQueue.global().async {
    singleton.someMethod()
}

Use Serial Dispatch Queues when

  • Your application requires simple and straightforward thread safety.
  • You need to ensure that only one thread accesses or modifies shared resources at any given time.
  • Your operations are mostly sequential and don’t require concurrent execution.

Use Actors when

  • You are using Swift’s concurrency features and want a modern, structured approach to managing state.
  • You prefer automatic management of thread safety without explicit locks or queues.
  • Your application involves complex state management that benefits from isolated state encapsulation.

Use Concurrent Queues with Barrier Flags when

  • Your application has a high volume of read operations and relatively fewer write operations.
  • You need to optimize for concurrent reads while ensuring thread-safe writes.
  • You want to balance performance and safety by allowing multiple threads to read data concurrently and synchronizing write operations.

Stay Ahead of the Curve—Follow Us on Twitter!

1 thought on “Singleton design patterns in Swift”

Leave a Comment