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
foo
is just anInt
computed property. The_foo
is theLazy<Int>
.So, in your
a["key"] = 5
example, it will not use your property wrapper’s subscript operator. It willget
the value associated witha
, use the dictionary’s own subscript operator to update that value (not the property wrapper’s subscript operator), and then it willset
the value associated witha
.That’s all the property wrapper is doing, providing the
get
andset
accessors. E.g., the declaration:translates to:
Any other methods you define are only accessible through
_a
in this example, nota
(which is just a computed property that gets and sets thewrappedValue
of_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
CustomDebugStringConvertible
conformance, 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.