Created
December 26, 2020 18:00
-
-
Save ollieatkinson/6e3d9f0547819b7a737cd7d9fd504c2a to your computer and use it in GitHub Desktop.
Atomic read/write access for collections
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class AtomicCollection<C> where C: Collection { | |
var base: C | |
let queue: DispatchQueue | |
init(_ base: C, label: String, qos: DispatchQoS = .unspecified, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil) { | |
self.base = base | |
self.queue = DispatchQueue( | |
label: label, | |
qos: qos, | |
attributes: .concurrent, | |
autoreleaseFrequency: autoreleaseFrequency, | |
target: target | |
) | |
} | |
} | |
extension AtomicCollection: Collection { | |
typealias Index = C.Index | |
typealias Element = C.Element | |
typealias SubSequence = C.SubSequence | |
var startIndex: Index { | |
queue.sync { base.startIndex } | |
} | |
var endIndex: Index { | |
queue.sync { base.endIndex } | |
} | |
var count: Int { | |
queue.sync { base.count } | |
} | |
subscript(position: Index) -> Element { | |
queue.sync { base[position] } | |
} | |
subscript(bounds: Range<Index>) -> SubSequence { | |
queue.sync { base[bounds] } | |
} | |
func index(after i: Index) -> Index { | |
queue.sync { base.index(after: i) } | |
} | |
func index(_ i: Index, offsetBy distance: Int) -> Index { | |
queue.sync { base.index(i, offsetBy: distance) } | |
} | |
func index(_ i: Index, offsetBy distance: Int, limitedBy limit: Index) -> Index? { | |
queue.sync { base.index(i, offsetBy: distance, limitedBy: limit) } | |
} | |
@inlinable | |
public func distance(from start: C.Index, to end: C.Index) -> Int { | |
queue.sync { base.distance(from: start, to: end) } | |
} | |
} | |
extension AtomicCollection: BidirectionalCollection where C: BidirectionalCollection { | |
func index(before i: Index) -> Index { | |
queue.sync { base.index(before: i) } | |
} | |
} | |
extension AtomicCollection: RandomAccessCollection where C: RandomAccessCollection {} | |
extension AtomicCollection: LazySequenceProtocol where C: LazySequenceProtocol {} | |
extension AtomicCollection: LazyCollectionProtocol where C: LazyCollectionProtocol {} | |
extension AtomicCollection: MutableCollection where C: MutableCollection { | |
subscript(position: Index) -> Element { | |
get { | |
queue.sync { base[position] } | |
} | |
set { | |
queue.async(flags: .barrier) { [self] in base[position] = newValue } | |
} | |
} | |
subscript(bounds: Range<Index>) -> SubSequence { | |
get { | |
queue.sync { base[bounds] } | |
} | |
set { | |
queue.async(flags: .barrier) { [self] in base[bounds] = newValue } | |
} | |
} | |
} | |
extension AtomicCollection: Equatable where C.Element: Equatable { | |
static func == (lhs: AtomicCollection, rhs: AtomicCollection) -> Bool { | |
lhs.elementsEqual(rhs, by: ==) | |
} | |
} | |
extension AtomicCollection: Hashable where C.Element: Hashable { | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(queue) | |
for element in self { | |
hasher.combine(element) | |
} | |
} | |
} | |
extension AtomicCollection: AnyDictionary where C: AnyDictionary { | |
typealias Key = C.Key | |
typealias Value = C.Value | |
var keys: C.Keys { | |
queue.sync { base.keys } | |
} | |
var values: C.Values { | |
queue.sync { base.values } | |
} | |
subscript(key: Key) -> Value? { | |
get { | |
return queue.sync { base[key] } | |
} | |
set { | |
queue.async(flags: .barrier) { [self] in base[key] = newValue } | |
} | |
} | |
subscript(key: Key, default defaultValue: @escaping @autoclosure () -> Value) -> Value { | |
get { | |
return queue.sync { base[key, default: defaultValue()] } | |
} | |
set { | |
queue.async(flags: .barrier) { [self] in base[key, default: defaultValue()] = newValue } | |
} | |
} | |
func index(forKey: Key) -> Index? { | |
queue.sync { base.index(forKey: forKey) } | |
} | |
func mapValues<T>(_ transform: (Value) throws -> T) rethrows -> [Key : T] { | |
try queue.sync { try base.mapValues(transform) } | |
} | |
func compactMapValues<T>(_ transform: (Value) throws -> T?) rethrows -> [Key : T] { | |
try queue.sync { try base.compactMapValues(transform) } | |
} | |
func updateValue(_ value: Value, forKey key: Key) -> Value? { | |
queue.sync { base.updateValue(value, forKey: key) } | |
} | |
func merge<S>(_ other: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S: Sequence, S.Element == (Key, Value) { | |
try queue.sync { try base.merge(other, uniquingKeysWith: combine) } | |
} | |
func merge(_ other: [Key: Value], uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows { | |
try queue.sync { try base.merge(other, uniquingKeysWith: combine) } | |
} | |
func merging<S>(_ other: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [Key: Value] where S: Sequence, S.Element == (Key, Value) { | |
try queue.sync { try base.merging(other, uniquingKeysWith: combine) } | |
} | |
func merging(_ other: [Key: Value], uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [Key: Value] { | |
try queue.sync { try base.merging(other, uniquingKeysWith: combine) } | |
} | |
func remove(at index: Index) -> Element { | |
queue.sync { base.remove(at: index) } | |
} | |
func removeValue(forKey key: Key) -> Value? { | |
queue.sync { base.removeValue(forKey: key) } | |
} | |
func removeAll(keepingCapacity keepCapacity: Bool) { | |
queue.async(flags: .barrier) { [self] in base.removeAll(keepingCapacity: keepCapacity) } | |
} | |
} | |
extension Collection { | |
func atomic( | |
_ label: String = "co.uk.thousandyears.atomic-collection.\(UUID().uuidString)", | |
qos: DispatchQoS = .unspecified, | |
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, | |
target: DispatchQueue? = nil | |
) -> AtomicCollection<Self> { | |
.init(self, label: label, qos: qos, autoreleaseFrequency: autoreleaseFrequency, target: target) | |
} | |
} |
let dictionary = [String: Int]().atomic()
let group = DispatchGroup()
for index in 0..<1000 {
group.enter()
DispatchQueue.global(qos: .utility).async {
dictionary["item-\(index)"] = index
group.leave()
}
}
for index in (0..<1000).reversed() {
group.enter()
DispatchQueue.global(qos: .utility).async {
dictionary["item-\(index)"] = index
group.leave()
}
}
for index in 0..<1000 {
group.enter()
DispatchQueue.global().async {
dictionary["item-\(index)"] = index
group.leave()
}
}
for index in (0..<1000).reversed() {
group.enter()
DispatchQueue.global().async {
dictionary["item-\(index)"] = index
group.leave()
}
}
group.notify(queue: .main) {
for (key, value) in dictionary {
assert(key == "item-\(value)")
}
print("✅")
exit(0)
}
dispatchMain()
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AnyDictionary.swift