观察 Swift KeyPaths 的列表

Observing a list of Swift KeyPaths

我有以下代码,它在两个属性上添加了一个观察者:

    obs = []

    let xa = self.observe(\CirclePolygon.radius, options: [.new]) { (op, ch) in
        callback()
    }
    obs.append(xa)


    let xb = self.observe(\CirclePolygon.origin.x, options: [.new]) { (op, ch) in
        callback()
    }
    obs.append(xb)

它有效,但我不喜欢重复。我试图传递一个 KeyPaths 列表:

    obs = []

    let keyPaths = [\CirclePolygon.radius, \CirclePolygon.origin.x]
    for keyPath in keyPaths {
        let xa = self.observe(keyPath, options: [.new]) { (op, ch) in
            callback()
        }
        obs.append(xa)
    }

但是我得到编译器错误:Generic parameter 'Value' could not be inferred

有办法吗?

首先,请记住 KVO 仅适用于 NSObject(或子类)的实例。因此,如果 CirclePolygonorigin 属性 是 CGPoint 或其他 struct,当您尝试注册 \CirclePolygon.origin.x 的观察者,因为 KVO 无法应用于 CGPoint.

x 属性

其次,你给的关键路径有不同的类型。假设(为了解决我的第一点)你想观察 radius (a Double) 和 origin (a CGPoint)。关键路径有不同的类型:

\CirclePolygon.radius   // type: KeyPath<CirclePolygon, Double>
\CirclePolygon.origin   // type: KeyPath<CirclePolygon, CGPoint>

如果您尝试将它们放在一个数组中,而不明确指定数组的类型,Swift 将推导如下:

let keyPaths = [\CirclePolygon.origin, \CirclePolygon.radius]
// deduced type of keyPaths: [PartialKeyPath<CirclePolygon>]

推导出[PartialKeyPath<CirclePolygon>]是因为PartialKeyPath<CirclePolygon>KeyPath<CirclePolygon, Double>KeyPath<CirclePolygon, CGPoint>最近的共同祖先。

因此,在遍历数组的 for 循环中,您将传递静态类型为 PartialKeyPath<CirclePolygon> 的对象作为 observe(_:options:changeHandler:)keyPath 参数] 方法。不幸的是,该方法是 declared as follows:

public func observe<Value>(
        _ keyPath: KeyPath<Self, Value>,
        options: NSKeyValueObservingOptions = [],
        changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void)
    -> NSKeyValueObservation

也就是说,该方法需要 KeyPath<Self, Value> 但您传递的是 PartialKeyPath<Self>,因此 Swift 会发出错误。与一般类型问题的 Swift 错误一样,该错误不是很有用。

你不能通过转换来解决这个问题,因为你不能(例如)将 KeyPath<CirclePolygon, Double> 转换为 KeyPath<CirclePolygon, Any>

一种解决方案可能是使用本地函数来封装公共代码,如下所示:

func register(callback: @escaping () -> ()) -> [NSKeyValueObservation] {
    func r<Value>(_ keyPath: KeyPath<CirclePolygon, Value>) -> NSKeyValueObservation {
        return observe(keyPath, options: []) { (_, _) in
            callback()
        }
    }

    return [
        r(\.radius),
        r(\.origin),
    ]
}