Skip to content

Instantly share code, notes, and snippets.

@IuriiIaremenko
Last active September 13, 2022 06:55
Show Gist options
  • Save IuriiIaremenko/5e87b99d587796d9d825cd619efa093b to your computer and use it in GitHub Desktop.
Save IuriiIaremenko/5e87b99d587796d9d825cd619efa093b to your computer and use it in GitHub Desktop.
ConcurrentSafe view Property Wrapper
//
// ConcurrentSafe.swift
//
// Created by Iurii Iaremenko on 01.02.2021.
//
import Foundation
/// Property wrapper for safe access and modification of the value across multiple thread/queues.
@propertyWrapper
struct ConcurrentSafe<Value> {
private let lock: NSRecursiveLock
private var value: Value
init(wrappedValue: Value) {
lock = .init()
value = wrappedValue
lock.name = String(describing: value)
}
var wrappedValue: Value {
get {
assert(!(self is Invalid || value is Invalid), "Error please use read function instead!")
lock.lock()
defer { lock.unlock() }
return value
}
set {
/// Mutation of the array, dictionary, set, etc must be performed inside the mutate function otherwise data race will occur.
assert(!(self is Invalid || value is Invalid), "Error please use mutate function instead!")
lock.lock()
defer { lock.unlock() }
value = newValue
}
}
/// Safely read the Value. Useful for Arrays or Dictionaries, because direct access could cause data race due to COW.
///
/// Example of usage:
///
/// var value: Int?
/// _array.read {
/// value = $0.first
/// }
/// _dictionary.read { cachedDictionary in
/// value = cachedDictionary["key"]
/// }
public func read(block: (Value) -> Void) {
lock.lock()
defer { lock.unlock() }
block(value)
}
/// Mutate the Value
///
/// Example of usage
///
/// _array.mutate {
/// $0.append(index)
/// }
/// _dictionary.mutate {
/// $0[i] = "ConcurrentSafe"
/// }
mutating func mutate(_ mutation: (inout Value) -> Void) {
lock.lock()
defer { lock.unlock() }
mutation(&value)
}
/// Mutate the Value with throwing mutation
///
/// Example of usage
///
/// _array.mutate {
/// $0.append(index)
/// }
/// _dictionary.mutate {
/// $0[i] = "ConcurrentSafe"
/// }
mutating func mutate(_ mutation: (inout Value) throws -> Void) rethrows {
lock.lock()
defer { lock.unlock() }
try mutation(&value)
}
}
private protocol Invalid { }
// COW (copy on write) is not thread safe and cases data race on during direct read and write
extension ConcurrentSafe: Invalid where Value: Sequence { }
extension Array: Invalid { }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment