Atomic property wrapper in Swift
September 12, 2019
#swift
#concurrency
Property Wrappers are one of the key features introduced in Swift 5.1 and described in Swift Evolution Proposal SE-0258. It brings an option to eliminate the boilerplate code and get more clean APIs.
You might be familiar with Property Wrappers already if you’ve tried SwiftUI or Combine frameworks and seen @State
, @Binding
, @ObservedObject
, @EnvironmentObject
or @Published
attributes that define property wrappers. Also, it looks quite similar to what is known as Delegated Properties in Kotlin.
Let’s take a closer look at this feature and check how can we use it to define atomic properties in Swift.
Atomic and Non-Atomic Properties
Atomic property is one of the commonly-requested Swift features, that exists in Objective-C. By default, an Objective-C property is atomic. Defining a property as atomic should guarantee that it can be safely read and written from different threads. Non-atomic properties have no guarantee regarding the returned value, but they come with enhanced speed of accessing these properties.
Note: Property atomicity is not synonymous with an object’s thread safety.
Consider an XYZPerson object in which both a person’s first and last names are changed using atomic accessors from one thread. If another thread accesses both names at the same time, the atomic getter methods will return complete strings (without crashing), but there’s no guarantee that those values will be the right names relative to each other. If the first name is accessed before the change, but the last name is accessed after the change, you’ll end up with an inconsistent, mismatched pair of names.
To implement atomic property in Swift we can use a lock. Lock is a mechanism that controls access to a shared resource or critical section of your code by multiple threads. There are many kinds of locks that are available in Swift via different Apple’s frameworks APIs:
NSLock
,pthread_mutex_t
- mutex that allows access to only one thread at a time. It blocks all other threads until the mutex is released by the holder.NSRecursiveLock
,objc_sync_enter/objc_sync_exit
- recursive lock that allows a thread to acquire the lock multiple times before releasing it.os_unfair_lock
,OSSpinLock
(deprecated since iOS 10) - spinlock that polls a lock condition repeatedly until that becomes true. It is efficient if threads are blocked for a short time.pthread_rwlock_t
- read-write lock which allows many reader threads and a single writer thread at a time. Should be used when reading is done more often than writing.DispatchSemaphore
- semaphore that restricts the number of threads than can access some resource through the use of a counter.OperationQueue
,DispatchQueue
- queues that can be configured to achieve a read/write lock or a mutex behavior.
All of the options mentioned above have their pros and cons and should be used according to your requirements. If you’d like to dive deeply into this topic, I’d suggest checking the Threading Programming Guide and Mike Ash blog post.
Leveraging property wrappers to define atomic properties
Atomic property could be easily expressed as a property wrapper. To create a property wrapper we should define a struct with @propertyWrapper
attribute. This attribute comes with the requirement of having a wrappedValue
property implemented.
@propertyWrapper
struct Atomic<Value> {
private var value: Value
private let lock = NSLock()
init(wrappedValue value: Value) {
self.value = value
}
var wrappedValue: Value {
get { return load() }
set { store(newValue: newValue) }
}
func load() -> Value {
lock.lock()
defer { lock.unlock() }
return value
}
mutating func store(newValue: Value) {
lock.lock()
defer { lock.unlock() }
value = newValue
}
}
Here we define a generic struct type Atomic
, that uses NSLock
under the hood to provide mutually exclusive access to the internal value.
Eventually, we can define an atomic property with:
@Atomic var counter: Int = 1
Conclusion
The property wrappers are an extremely powerful feature that helps you to get concise and reusable code for the properties you write. In this article, we’ve discussed only one of the many possible use cases. Feel free to follow me on Twitter and ask questions related to this post.
Thanks for reading!