Monday, October 29, 2018

NSKeyValueObservingCustomization Is Fundamentally Broken

Lily Ballard (tweet):

The breakage is the fact that implementing it relies on being able to do equality testing on key paths, but equality testing on key paths doesn’t work in the face of subclassing. By that I mean given the following code

class Foo: NSObject {
    @objc dynamic var name: String?
}
class Bar: Foo {}

The expression \Foo.name == \Bar.name returns false even though Bar is just inheriting its property from Foo. This means that an implementation of NSKeyValueObservingCustomization cannot possibly work properly on anything besides a non-final class. Even if keypath construction in this instance were changed such that \Bar.name returns the same thing as \Foo.name, the same cannot be done for the more complicated case[…]

I guess the workaround is to use the string-based Objective-C methods instead of the AnyKeyPath Swift ones.

See also: SR-9077.

NSKeyValueObservingCustomization relies on a global table to map String key paths back into AnyKeyPath values. However, as the string keypath does not include the root type, this means that observing properties with the same name on separate objects will overwrite each other in the global table. In a single-threaded scenario this is acceptable as the NSKeyValueObservingCustomization methods are invoked synchronously when the observation is created, and the global table is populated immediately prior to creating the observation. However, in a multithreaded scenario, the global keypath table could be overwritten with a different keypath prior to invoking the NSKeyValueObservingCustomization method.

Comments RSS · Twitter

Leave a Comment