简体   繁体   中英

Fatal error: could not demangle keypath type

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:

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()

It turns out the Swift compiler was not properly handling key paths containing the covariant Self type. In case you need a refresher, the covariant Self type or dynamic Self type allows you to specify that a method or property always returns the type of self even if the class is subclassed. For example:

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

But your example doesn't use dynamic Self : Well actually it does, in the context of a convenience initializer, the type of self is actually the dynamic Self type, because a convenience initializer can also be called to initialize a subclass of your class V .

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 type ReferenceWritableKeyPath<Self, CGFloat> . The type system doesn't allow you to use dynamic Self 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 your V class, followed by the suffix 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 , 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. Dynamic Self only matters to enforce program correctness at compile time; it can and should be stripped out of the program before runtime.

It is because of convenience init(), Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer's parameters set to default values.

Since, in convenience init() the super class property can't be accessed, you are getting Fatal error: could not demangle keypath type.

If you do the same in the designated init() it will work as it is sure that super class is initialized. Below code will print 0.5 as expected:

  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

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM