Avoid KVC Crashes in setValue on let Properties at Compile Time (and KVO Problems by Forgetting @objc or dynamic)

I did indeed assume that intimate knowledge of KVO was not present, because there was no mention whatsoever of any understanding of the current requirement for dynamic. I could only read the expression of surprise. This link to the doc and further explanation (with a excellent extra link by @fabb on the various method dispatching techniques) did look necessary to me. This is a topic which can't go forward without the required level of knowledge. The general KVO mechanism does not live only in code: you have to read text in order to understand it.

The idea Joe suggested above was to warn for patterns like observe(\.s, where there's a literal key path passed to observe. This doesn't cover all cases, but it does cover some.

I don't have ideas beyond this, unfortunately.

I didn't do a very good when trying to weigh in on the issue earlier, so let me try again.

There are some intractable problems about fixing the issues that you describe. That's because KVC and KVO are Obj-C mechanisms that do not translate into anything that Swift can get a full grip on.

For a start, it isn't true that the dynamic keyword is always required for KVO and KVC. It's not even true that an @objc property is always required. Those things are required only when you want to adopt automatic KVC/KVO behavior. You want that most of the time, but there's plenty of Obj-C code that doesn't want it.

What dynamic does is to ensure the the property is always accessed in Swift using dynamic (i.e. Obj-C runtime) dispatch. That ensures the property produces automatic KVO notifications without additional code in the property setter. If, however, the notifications are generated by code (using the willChange… and didChange… families of methods), the automatic notifications are usually not wanted and usually need to be suppressed.

Manual KVO notifications will also work (in the sense that notifications are produced) whether the property is @objc or not. Further, they will work whether the property exists or not. Of course, the KeyPath syntax won't compile if the property doesn't exist, but that's actually a kind of limitation in the Swift implementation compared to the way things are done in Obj-C.

Similarly, KVC doesn't absolutely depend on the @objc attribute of the property, or (again) the existence of the property. These are only requirements if you want KVC to find your implementations automatically, but there are manual ways to intercept KVC accesses and to resolve them any way you want.

This is all part of Obj-C's highly dynamic and polymorphic design, and is generally regarded as a good thing over in the Obj-C world. It just turns out to be a pretty bad thing in the Swift world.

Support for KVC/KVO in Swift is pretty much a hack, that works well only if you stick to the well-lighted pathways. It's that way because it's a known problem that KVC/KVO isn't ever likely to fit into the Swift language. Most of what we have just plugs the hole until someone designs a Swift-native mechanism that replaces KVC/KVO.

The current KeyPath is actually a first step in that direction, and a pretty good one. For simple (automatic) cases, the Swift observation overlay is arguably better than the Obj-C methods it's based on. KeyPath also has uses outside the context of KVC/KVO — such as MemoryLayout.offset(of:).

For now, though, it's just not worth spending a lot of time trying to nicely Swiftify a mechanism that doesn't fit the language.

4 Likes

For what it's worth, we have [SR-5115] KeyPath-based KVO: No diagnostic for inaccessible KVO KeyPaths · Issue #47691 · apple/swift · GitHub tracking adding a diagnostic for the obvious cases. In the fullness of time, I hope we'll migrate KeyPath to a protocol-based based design, which will allow traits like "writable" and "observable" (and new traits on top of that) to be incorporated into the type system in a composable way, but without generalized existentials, protocol-based key paths would be difficult to work with.

4 Likes

NSObject - Sneaking danger into Swift land since 2014 :sweat_smile:

That's unfortunate that Apple Developer Documentation does not mention any of this.