简体   繁体   中英

Observing a list of Swift KeyPaths

I have the following code which adds an observer on two properties:

    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)

It works, but I don't like the duplciation. I have tried to pass a list of 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)
    }

But I get the compiler error: Generic parameter 'Value' could not be inferred

Is there anyway to do this?

First of all, remember that KVO only works on instances of NSObject (or a subclass). So if the origin property of CirclePolygon is a CGPoint or some other struct , you'll get a run-time error when you try to register an observer of \\CirclePolygon.origin.x , because KVO cannot apply to the x property of a CGPoint .

Second, the key paths you gave have different types. Let's say (to address my first point) you want to observe radius (a Double ) and origin (a CGPoint ). The key paths have different types:

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

If you try to put these in a single array, without explicitly specifying the type of the array, Swift will deduce it as follows:

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

It deduces [PartialKeyPath<CirclePolygon>] because PartialKeyPath<CirclePolygon> is the nearest common ancestor of both KeyPath<CirclePolygon, Double> and KeyPath<CirclePolygon, CGPoint> .

So, in the for loop where you iterate over the array, you're passing an object with static type PartialKeyPath<CirclePolygon> as the keyPath argument of the observe(_:options:changeHandler:) method. Unfortunately, that method is declared as follows :

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

Which is to say, the method requires a KeyPath<Self, Value> but you're passing a PartialKeyPath<Self> , so Swift emits an error. As is often the case with Swift errors around generic type problems, the error isn't very helpful.

You cannot solve this problem by casting, because you cannot (for example) cast KeyPath<CirclePolygon, Double> to KeyPath<CirclePolygon, Any> .

One solution may be to use a local function to encapsulate the common code, like this:

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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