繁体   English   中英

不调用 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的下标AtomicDicationary没有被调用!!

有人对为什么会这样有任何解释吗?

属性包装器只是为基本访问器方法提供一个接口,仅此而已。 它不会拦截下标或其他方法。

最初的属性包装提案SE-0258向我们展示了幕后发生的事情。 它考虑了一个假设的属性包装器Lazy ,其中:

财产申报

@Lazy var foo = 1738

翻译成:

 private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738) var foo: Int { get { return _foo.wrappedValue } set { _foo.wrappedValue = newValue } }

请注意, foo只是一个Int计算属性。 _fooLazy<Int>

因此,在您a["key"] = 5示例中,它不会使用您的属性包装器的下标运算符。 它将geta关联的值,使用字典自己的下标运算符来更新该值(而不是属性包装器的下标运算符),然后它将seta关联的值。

这就是属性包装器所做的全部工作,提供getset访问器。 例如,声明:

@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 (它只是一个计算属性,用于获取和设置_awrappedValue )。


所以,你最好只为你的“原子字典”定义一个合适的类型:

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 }
    }
}

与包装值的所有交互都必须同步。


显然,您可以将这种通用模式与您想要的任何同步机制一起使用,例如,上面的读写器模式、GCD 串行队列、锁、参与者等。(读写器模式具有天然的吸引力,但在实践中,通常是更好的机制。)


不用说,上面假设下标级原子性就足够了。 人们应该始终警惕通用线程安全 collections,因为我们代码的正确性通常依赖于更高级别的同步。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM