Skip to content

Instantly share code, notes, and snippets.

@CassiusPacheco
Last active March 16, 2020 03:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CassiusPacheco/48a38ad3a72927c0881a54889918e9b3 to your computer and use it in GitHub Desktop.
Save CassiusPacheco/48a38ad3a72927c0881a54889918e9b3 to your computer and use it in GitHub Desktop.
@atomic Property Wrapper
import Foundation
/// This property wrapper offers a locking mechanism for accessing/mutating values in a safer
/// way. Bear in mind that operations such as `+=` or executions of `if let` to read and then
/// mutate values are *unsafe*. Each time you access the variable to read it, it acquires the lock,
/// then once the read is finished it releases it. The following operation is to mutate the value, which
/// requires the lock to be mechanism again, however, another thread may have already claimed the lock
/// in between these two operations and have potentially changed the value. This may cause unexpected
/// results or crashes.
/// In order to ensure you've acquired the lock for a certain amount of time use the `mutate` method.
@propertyWrapper
public final class Atomic<Value> {
private var value: Value
private let lock = NSLock()
public init(wrappedValue: Value) {
value = wrappedValue
}
public var wrappedValue: Value {
get {
lock.lock()
defer { lock.unlock() }
return value
}
set {
lock.lock()
defer { lock.unlock() }
value = newValue
}
}
/// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation.
public func mutate(_ mutation: (inout Value) -> Void) {
lock.lock()
defer { lock.unlock() }
mutation(&value)
}
/// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation.
/// This method returns a value specified in the `mutation` closure.
public func mutate<T>(_ mutation: (inout Value) -> T) -> T {
lock.lock()
defer { lock.unlock() }
return mutation(&value)
}
}
@CassiusPacheco
Copy link
Author

CassiusPacheco commented Feb 16, 2020

DispatchQueue.concurrentPerform(iterations: 100000) { (i) in
    // This code will crash on heavy concurrence usage,
    // because the `else if` checks if the array is empty
    // to then delete an object. However by the time it gets
    // to delete the object the value may have been changed
    // by another thread.
    //
    // if i % 2 == 0 {
    //      self.atomicArray.append(i)
    // } else if !self.atomicArray.isEmpty {
    //      self.atomicArray.removeLast()
    // }

    if i % 2 == 0 {
        self.atomicArray.append(i)
    } else {
        // Access the wrapper in order to synchronise the mutation
        // to ensure the value doesn't get changed by another thread
        // during this mutation.
        self._atomicArray.mutate {
            if !$0.isEmpty {
                $0.removeLast()
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment