不调用 PropertyWrapper 下标。为什么?
PropertyWrapper subscript is not called. WHY?
我正在实现自己的 AtomicDictionary
属性 包装器,如下所示:
@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
}
}
现在,当我使用它时如下:
class ViewController: UIViewController {
@AtomicDictionary var a: [String: Int]
override func viewDidLoad() {
super.viewDidLoad()
self.a["key"] = 5
}
}
AtomicDicationary
的下标函数没有被调用!!
有人能解释这是为什么吗?
Property wrappers 只是为基本访问器方法提供一个接口,仅此而已。它不会拦截下标或其他方法。
最初的 属性 包装提案 SE-0258 向我们展示了幕后发生的事情。它考虑了一个假设的 属性 包装器 Lazy
,其中:
The property declaration
@Lazy var foo = 1738
translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
请注意 foo
只是一个 Int
计算出来的 属性。 _foo
是 Lazy<Int>
.
因此,在您的 a["key"] = 5
示例中,它不会使用您的 属性 包装器的下标运算符。它将 get
与 a
关联的值,使用字典自己的下标运算符来更新该值(不是 属性 包装器的下标运算符),然后它将 set
与 a
.
关联的值
这就是 属性 包装器所做的全部工作,提供 get
和 set
访问器。例如,声明:
@AtomicDictionary var a: [String: Int]
转换为:
private var _a: AtomicDictionary<String, Int> = AtomicDictionary<String, Int>(wrappedValue: [:])
var a: [String: Int] {
get { return _a.wrappedValue }
set { _a.wrappedValue = newValue }
}
在此示例中,您定义的任何其他方法只能通过 _a
访问,而不是 a
(这只是一个计算的 属性,用于获取和设置 wrappedValue
_a
).
所以,你最好只为你的“原子字典”定义一个合适的类型:
public class AtomicDictionary<Key: Hashable, Value> {
private var wrappedValue: [Key: Value]
private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)", attributes: .concurrent)
init(_ wrappedValue: [Key: Value] = [:]) {
self.wrappedValue = wrappedValue
}
public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}
set {
queue.async(flags: .barrier) {
self.wrappedValue[key] = newValue
}
}
}
}
和
let a = AtomicDictionary<String, Int>()
这给了你想要的行为。
如果您要提供 CustomDebugStringConvertible
一致性,请确保也在那里使用您的同步机制:
extension AtomicDictionary: CustomDebugStringConvertible {
public var debugDescription: String {
queue.sync { wrappedValue.debugDescription }
}
}
所有与包装值的交互都必须同步。
显然,您可以将此通用模式与您想要的任何同步机制一起使用,例如,上面的 reader-writer 模式、GCD 串行队列、锁、参与者等。(reader-writer 模式有一个自然的吸引力,但在实践中,通常有更好的机制。)
不用说了,以上假设subscript-level原子性就足够了。人们应该始终警惕通用 thread-safe 集合,因为我们代码的正确性通常依赖于 higher-level 同步。
我正在实现自己的 AtomicDictionary
属性 包装器,如下所示:
@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
}
}
现在,当我使用它时如下:
class ViewController: UIViewController {
@AtomicDictionary var a: [String: Int]
override func viewDidLoad() {
super.viewDidLoad()
self.a["key"] = 5
}
}
AtomicDicationary
的下标函数没有被调用!!
有人能解释这是为什么吗?
Property wrappers 只是为基本访问器方法提供一个接口,仅此而已。它不会拦截下标或其他方法。
最初的 属性 包装提案 SE-0258 向我们展示了幕后发生的事情。它考虑了一个假设的 属性 包装器 Lazy
,其中:
The property declaration
@Lazy var foo = 1738
translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738) var foo: Int { get { return _foo.wrappedValue } set { _foo.wrappedValue = newValue } }
请注意 foo
只是一个 Int
计算出来的 属性。 _foo
是 Lazy<Int>
.
因此,在您的 a["key"] = 5
示例中,它不会使用您的 属性 包装器的下标运算符。它将 get
与 a
关联的值,使用字典自己的下标运算符来更新该值(不是 属性 包装器的下标运算符),然后它将 set
与 a
.
这就是 属性 包装器所做的全部工作,提供 get
和 set
访问器。例如,声明:
@AtomicDictionary var a: [String: Int]
转换为:
private var _a: AtomicDictionary<String, Int> = AtomicDictionary<String, Int>(wrappedValue: [:])
var a: [String: Int] {
get { return _a.wrappedValue }
set { _a.wrappedValue = newValue }
}
在此示例中,您定义的任何其他方法只能通过 _a
访问,而不是 a
(这只是一个计算的 属性,用于获取和设置 wrappedValue
_a
).
所以,你最好只为你的“原子字典”定义一个合适的类型:
public class AtomicDictionary<Key: Hashable, Value> {
private var wrappedValue: [Key: Value]
private let queue = DispatchQueue(label: "atomicDictionary.\(UUID().uuidString)", attributes: .concurrent)
init(_ wrappedValue: [Key: Value] = [:]) {
self.wrappedValue = wrappedValue
}
public subscript(key: Key) -> Value? {
get {
queue.sync {
wrappedValue[key]
}
}
set {
queue.async(flags: .barrier) {
self.wrappedValue[key] = newValue
}
}
}
}
和
let a = AtomicDictionary<String, Int>()
这给了你想要的行为。
如果您要提供 CustomDebugStringConvertible
一致性,请确保也在那里使用您的同步机制:
extension AtomicDictionary: CustomDebugStringConvertible {
public var debugDescription: String {
queue.sync { wrappedValue.debugDescription }
}
}
所有与包装值的交互都必须同步。
显然,您可以将此通用模式与您想要的任何同步机制一起使用,例如,上面的 reader-writer 模式、GCD 串行队列、锁、参与者等。(reader-writer 模式有一个自然的吸引力,但在实践中,通常有更好的机制。)
不用说了,以上假设subscript-level原子性就足够了。人们应该始终警惕通用 thread-safe 集合,因为我们代码的正确性通常依赖于 higher-level 同步。