I am implementing my own AtomicDictionary property wrapper as follows:
@propertyWrapper
public class AtomicDictionary<Key: Hashable, Value>: CustomDebugStringConvertible {
public var wrappedValue = [Key: Value]()
private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)",
attributes: .concurrent)
public init() {}
public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}
set {
queue.async(flags: .barrier) { [weak self] in
self?.wrappedValue[key] = newValue
}
}
}
public var debugDescription: String {
return wrappedValue.debugDescription
}
}
now, when I use it as follows:
class ViewController: UIViewController {
@AtomicDictionary var a: [String: Int]
override func viewDidLoad() {
super.viewDidLoad()
self.a["key"] = 5
}
}
The subscript function of the AtomicDicationary is not called!!
Does anybody have any explanation as to why that is?
Property wrappers merely provide an interface for the basic accessor methods, but that’s it. It’s not going to intercept subscripts or other methods.
The original property wrapper proposal SE-0258 shows us what is going on behind the scenes. It contemplates a hypothetical property wrapper,
Lazy, in which:Note that
foois just anIntcomputed property. The_foois theLazy<Int>.So, in your
a["key"] = 5example, it will not use your property wrapper’s subscript operator. It willgetthe value associated witha, use the dictionary’s own subscript operator to update that value (not the property wrapper’s subscript operator), and then it willsetthe value associated witha.That’s all the property wrapper is doing, providing the
getandsetaccessors. E.g., the declaration:translates to:
Any other methods you define are only accessible through
_ain this example, nota(which is just a computed property that gets and sets thewrappedValueof_a).So, you’re better off just defining a proper type for your “atomic dictionary”:
And
That gives you the behavior you want.
And if you are going to supply
CustomDebugStringConvertibleconformance, make sure to use your synchronization mechanism there, too:All interaction with the wrapped value must be synchronized.
Obviously you can use this general pattern with whatever synchronization mechanism you want, e.g., the above reader-writer pattern, GCD serial queue, locks, actors, etc. (The reader-writer pattern has a natural appeal, but, in practice, there are generally better mechanisms.)
Needless to say, the above presumes that subscript-level atomicity is sufficient. One should always be wary about general purpose thread-safe collections as often the correctness of our code relies on a higher-level of synchronization.