I have a simple class and I want to use keypath in the init
, something like this:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \.alpha] = 0.5
}
}
let v = View()
When I run this code I get a runtime error:
Fatal error: could not demangle keypath type from ' ����XD':
But, if I specify the type in keyPath it works fine:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \UIView.alpha] = 0.5
}
}
let v = View()
print(v.alpha) \\ prints 0.5
But, what's even stranger is that this code works:
class V: UIView {
convenience init() {
self.init(frame: .zero)
foo()
}
func foo() {
self[keyPath: \.alpha] = 0.5
}
}
let v = View()
print(v.alpha) \\ prints 0.5
What is the actual reason for this error?
Unsurprisingly, this is a compiler bug. In fact, it was reported only a couple weeks before you posted your question. The bug report contains a slightly simpler example that triggers the same crash:
It turns out the Swift compiler was not properly handling key paths containing the covariant
Self
type. In case you need a refresher, the covariantSelf
type or dynamicSelf
type allows you to specify that a method or property always returns the type ofself
even if the class is subclassed. For example:But your example doesn't use dynamic
Self
! Well actually it does: in the context of a convenience initializer, the type ofself
is actually the dynamicSelf
type, because a convenience initializer can also be called to initialize a subclass of your classV
.So what exactly went wrong? Well, the Swift compiler did not include any logic to handle dynamic
Self
when creating a key path. Under-the-hood, it essentially tried to emit a key path object of typeReferenceWritableKeyPath<Self, CGFloat>
. The type system doesn't allow you to use dynamicSelf
in that context, and the runtime was not expecting it. The strange error message you received was the result of trying to decode this unexpected object type, which was encoded as a 4-byte relative pointer to the metadata for yourV
class, followed by the suffixXD
indicating a dynamicSelf
type (hence the error message containing 4�
's followed byXD
). By playing around with different ways to create key paths involving dynamicSelf
, I came across a number of different crashes at both compile-time and runtime.I have submitted a fix for this bug. It turned out to be pretty simple: essentially, everywhere we find a dynamic
Self
when creating a key path, we just replace it with the static self and add downcasts when necessary. DynamicSelf
only matters to enforce program correctness at compile time; it can and should be stripped out of the program before runtime.