Swift Utilities — Thread Safe Property

Vyacheslav Ansimov
2 min readNov 19, 2023

--

Over the years as an iOS developer, I’ve accumulated a variety of tools and helpful items that make the development process easier. In this article, I want to share one of those tools. This will not be a long article. I’ll show how to use this utility and demonstrate it in action. I hope the article will be useful for you.

During development, data security in concurrent access is very important. In this article, I will show you how to create a thread-safe property using the @SynchronizedLock property wrapper.

@SynchronizedLock is a property wrapper in Swift that provides thread-safe access to a variable. This means that when reading and writing the value of the wrapped variable, these operations will be safely executed in different threads.

/// When using this property wrapper, you can ensure that reads and writes to the wrapped value are thread-safe.
/// Example:
///
///```swift
/// class SomeClass {
///
/// @SynchronizedLock var number: Int = 0
///
/// func main() {
/// DispatchQueue.global().async { [weak self] in
/// for _ in 1 ... 1000 {
/// self?.number = Int.random(in: 0 ..< 10)
/// }
/// }
/// DispatchQueue.global().async { [weak self] in
/// for _ in 1 ... 1000 {
/// self?.number = Int.random(in: 0 ..< 10)
/// }
/// }
/// }
/// }
///```
@propertyWrapper
public struct SynchronizedLock<Value> {
private var value: Value
private var lock = NSLock()

public var wrappedValue: Value {
get { lock.synchronized { value } }
set { lock.synchronized { value = newValue } }
}

public init(wrappedValue value: Value) {
self.value = value
}
}

private extension NSLock {

@discardableResult
func synchronized<T>(_ block: () -> T) -> T {
lock()
defer { unlock() }
return block()
}
}

How does it work?

To ensure thread safety, @SynchronizedLock uses NSLock. This ensures that only one thread can access the variable at a given time. The lock is released only after the operation is completed, allowing the next thread to enter the critical section.

Example of Use

Consider a class SomeClass, where we use @SynchronizedLock for thread-safe access to the variable count.

class SomeClass {

@SynchronizedLock var number: Int = 0

func main() {
DispatchQueue.global().async { [weak self] in
for _ in 1 ... 1000 {
self?.number = Int.random(in: 0 ..< 10)
}
}
DispatchQueue.global().async { [weak self] in
for _ in 1 ... 1000 {
self?.number = Int.random(in: 0 ..< 10)
}
}
}
}

In this example, two different threads attempt to modify the value of number simultaneously. Thanks to @SynchronizedLock, the increment operations are performed without conflicts and race conditions.

Advantages and Limitations

The main advantage of @SynchronizedLock lies in its ease of use and ensuring data safety in a multi-threaded environment. However, the use of locks can lead to reduced performance if they are used excessively or in a suboptimal context.

More stories

--

--