Fatal error: could not demangle keypath type
Fatal error: could not demangle keypath type
我有一个简单的 class,我想在 init
中使用 keypath,像这样:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \.alpha] = 0.5
}
}
let v = View()
当我 运行 此代码时,我收到 运行 时间错误:
Fatal error: could not demangle keypath type from ' ����XD':
但是,如果我在 keyPath 中指定类型,它就可以正常工作:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \UIView.alpha] = 0.5
}
}
let v = View()
print(v.alpha) \ prints 0.5
但是,更奇怪的是这段代码有效:
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
此错误的实际原因是什么?
因为convenience init(),convenience initializers是次要的,支持class的initializer。您可以定义便利初始化器,以从 与便利初始化器相同的 class 调用指定初始化器,并将指定初始化器的一些参数设置为默认值。
因为,为了方便 init() 无法访问超级 class 属性,您会收到致命错误:无法分解键路径类型。
如果您在指定的 init() 中执行相同操作,它将正常工作,因为它确保 super class 已初始化。下面的代码将按预期打印 0.5:
class View: UIView {
init() {
super.init(frame: .zero)
self[keyPath: \.alpha] = 0.5
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let v = View()
print(v.alpha) // prints 0.5
毫不奇怪,这是一个编译器错误。事实上,it was reported 就在您发布问题前几周。错误报告包含一个触发相同崩溃的稍微简单的示例:
class Foo: NSObject {
@objc let value: String = "test"
func test() {
let k1 = \Foo.value // Ok
let k2 = \Self.value // Fatal error: could not demangle keypath type from '�: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/KeyPath.swift, line 2623
}
}
Foo().test()
原来 Swift 编译器没有正确处理包含协变 Self
类型的关键路径。如果您需要复习,covariant Self
type or dynamic Self
type 允许您指定一个方法或 属性 总是 returns self
的类型,即使 class 是 sub class编辑。例如:
class Foo {
var invariant: Foo { return self }
var covariant: Self { return self }
}
class Bar: Foo {}
let a = Bar().invariant // a has compile-time type Foo
let b = Bar().covariant // b has compile-time type Bar
func walkInto(bar: Bar) {}
walkInto(bar: a) // error: cannot convert value of type 'Foo' to expected argument type 'Bar'
walkInto(bar: b) // works
但是你的例子没有使用动态Self
!实际上它确实如此:在便利初始化器的上下文中,self
的类型实际上是动态 Self
类型,因为便利初始化器也可以被调用来初始化 subclass你的 class V
.
那么究竟出了什么问题呢?好吧,Swift 编译器在创建关键路径时不包含任何处理动态 Self
的逻辑。在幕后,它本质上是试图发出类型为 ReferenceWritableKeyPath<Self, CGFloat>
的关键路径对象。类型系统不允许您在该上下文中使用动态 Self
,并且运行时不期望它。您收到的奇怪错误消息是尝试解码此意外对象类型的结果,该对象类型被编码为指向 V
class 元数据的 4 字节相对指针,后跟后缀 XD
indicating a dynamic Self
type (hence the error message containing 4 �
's followed by XD
). By playing around with different ways to create key paths involving dynamic Self
,我在编译时和运行时都遇到过许多不同的崩溃。
我已经提交 a fix 这个错误。事实证明它非常简单:基本上,在创建关键路径时,我们在任何地方找到动态 Self
,我们只需将其替换为静态 self 并在必要时添加向下转型。 Dynamic Self
只在编译时保证程序的正确性;它可以而且应该在运行前从程序中删除。
我有一个简单的 class,我想在 init
中使用 keypath,像这样:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \.alpha] = 0.5
}
}
let v = View()
当我 运行 此代码时,我收到 运行 时间错误:
Fatal error: could not demangle keypath type from ' ����XD':
但是,如果我在 keyPath 中指定类型,它就可以正常工作:
class V: UIView {
convenience init() {
self.init(frame: .zero)
self[keyPath: \UIView.alpha] = 0.5
}
}
let v = View()
print(v.alpha) \ prints 0.5
但是,更奇怪的是这段代码有效:
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
此错误的实际原因是什么?
因为convenience init(),convenience initializers是次要的,支持class的initializer。您可以定义便利初始化器,以从 与便利初始化器相同的 class 调用指定初始化器,并将指定初始化器的一些参数设置为默认值。
因为,为了方便 init() 无法访问超级 class 属性,您会收到致命错误:无法分解键路径类型。
如果您在指定的 init() 中执行相同操作,它将正常工作,因为它确保 super class 已初始化。下面的代码将按预期打印 0.5:
class View: UIView {
init() {
super.init(frame: .zero)
self[keyPath: \.alpha] = 0.5
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let v = View()
print(v.alpha) // prints 0.5
毫不奇怪,这是一个编译器错误。事实上,it was reported 就在您发布问题前几周。错误报告包含一个触发相同崩溃的稍微简单的示例:
class Foo: NSObject {
@objc let value: String = "test"
func test() {
let k1 = \Foo.value // Ok
let k2 = \Self.value // Fatal error: could not demangle keypath type from '�: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/KeyPath.swift, line 2623
}
}
Foo().test()
原来 Swift 编译器没有正确处理包含协变 Self
类型的关键路径。如果您需要复习,covariant Self
type or dynamic Self
type 允许您指定一个方法或 属性 总是 returns self
的类型,即使 class 是 sub class编辑。例如:
class Foo {
var invariant: Foo { return self }
var covariant: Self { return self }
}
class Bar: Foo {}
let a = Bar().invariant // a has compile-time type Foo
let b = Bar().covariant // b has compile-time type Bar
func walkInto(bar: Bar) {}
walkInto(bar: a) // error: cannot convert value of type 'Foo' to expected argument type 'Bar'
walkInto(bar: b) // works
但是你的例子没有使用动态Self
!实际上它确实如此:在便利初始化器的上下文中,self
的类型实际上是动态 Self
类型,因为便利初始化器也可以被调用来初始化 subclass你的 class V
.
那么究竟出了什么问题呢?好吧,Swift 编译器在创建关键路径时不包含任何处理动态 Self
的逻辑。在幕后,它本质上是试图发出类型为 ReferenceWritableKeyPath<Self, CGFloat>
的关键路径对象。类型系统不允许您在该上下文中使用动态 Self
,并且运行时不期望它。您收到的奇怪错误消息是尝试解码此意外对象类型的结果,该对象类型被编码为指向 V
class 元数据的 4 字节相对指针,后跟后缀 XD
indicating a dynamic Self
type (hence the error message containing 4 �
's followed by XD
). By playing around with different ways to create key paths involving dynamic Self
,我在编译时和运行时都遇到过许多不同的崩溃。
我已经提交 a fix 这个错误。事实证明它非常简单:基本上,在创建关键路径时,我们在任何地方找到动态 Self
,我们只需将其替换为静态 self 并在必要时添加向下转型。 Dynamic Self
只在编译时保证程序的正确性;它可以而且应该在运行前从程序中删除。