In Objective-C you have a distinction between atomic and nonatomic properties:
@property (nonatomic, strong) NSObject *nonatomicObject;
@property (atomic, st
Here is the atomic property wrapper that I use extensively. I made the actual locking mechanism a protocol, so I could experiement with different mechanisms. I tried semaphores, DispatchQueues
, and the pthread_rwlock_t
. The pthread_rwlock_t
was chosen because it appears to have the lowest overhead, and a lower chance of a priority inversion.
/// Defines a basic signature that all locks will conform to. Provides the basis for atomic access to stuff.
protocol Lock {
init()
/// Lock a resource for writing. So only one thing can write, and nothing else can read or write.
func writeLock()
/// Lock a resource for reading. Other things can also lock for reading at the same time, but nothing else can write at that time.
func readLock()
/// Unlock a resource
func unlock()
}
final class PThreadRWLock: Lock {
private var rwLock = pthread_rwlock_t()
init() {
guard pthread_rwlock_init(&rwLock, nil) == 0 else {
preconditionFailure("Unable to initialize the lock")
}
}
deinit {
pthread_rwlock_destroy(&rwLock)
}
func writeLock() {
pthread_rwlock_wrlock(&rwLock)
}
func readLock() {
pthread_rwlock_rdlock(&rwLock)
}
func unlock() {
pthread_rwlock_unlock(&rwLock)
}
}
/// A property wrapper that ensures atomic access to a value. IE only one thing can write at a time.
/// Multiple things can potentially read at the same time, just not during a write.
/// By using `pthread` to do the locking, this safer then using a `DispatchQueue/barrier` as there isn't a chance
/// of priority inversion.
@propertyWrapper
public final class Atomic {
private var value: Value
private let lock: Lock = PThreadRWLock()
public init(wrappedValue value: Value) {
self.value = value
}
public var wrappedValue: Value {
get {
self.lock.readLock()
defer { self.lock.unlock() }
return self.value
}
set {
self.lock.writeLock()
self.value = newValue
self.lock.unlock()
}
}
/// Provides a closure that will be called synchronously. This closure will be passed in the current value
/// and it is free to modify it. Any modifications will be saved back to the original value.
/// No other reads/writes will be allowed between when the closure is called and it returns.
public func mutate(_ closure: (inout Value) -> Void) {
self.lock.writeLock()
closure(&value)
self.lock.unlock()
}
}