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 只在编译时保证程序的正确性;它可以而且应该在运行前从程序中删除。