protocol typed array can't be downcast to concrete type array

蹲街弑〆低调 提交于 2019-11-28 21:26:45

Ignoring the optional binding for a moment and using a direct assignment:

let x = ps as [X]

the following runtime error is reported:

fatal error: array element cannot be bridged to Objective-C

That means the downcast from array of protocols to array of adopters requires obj-c binding. This can be easily solved by declaring the protocol as objc compatible:

@objc protocol P : class {
    var value:Int {get}
}

With that simple change, the code now works and no run time exception is raised.

Now the how is solved, but leaving the why an open issue. I don't have an answer yet, but I'll try to dig deeper on that.

Addendum: figure out the "why"

I spent some time investigating on this issue, and following is what I've come with.

We have a protocol and a class adopting it:

protocol P {}
class X : P {}

We create an array of P:

var array = [P]()

Converting the empty array to [X] works:

array as [X] // 0 elements

If we add an element to the array, a runtime error occurs:

array.append(X())
array as [X] // Execution was interrupted, reason: ...

The console output says that:

fatal error: array element cannot be bridged to Objective-C

So casting an array of protocol objects to an array of its adopter requires bridging. That justifies why @objc fixes the issue:

@objc protocol P {}
class X : P {}

var array = [P]()
array.append(X())
array as [X] // [X]

Sifting the documentation, I found out the reason for that to happen.

In order to perform the cast, the runtime has to check whether X conforms to the P protocol. The documentation clearly states that:

You can check for protocol conformance only if your protocol is marked with the @objc attribute

To verify that (not that I don't trust the documentation), I've used this code in the playground:

protocol P {}
class X : P {}

let x = X()
let y = x is P

but I get a different error, stating that:

Playground execution failed: <EXPR>:18:11: error: 'is' test is always true 
let y = x is P

Writing that in a "regular" project instead we get what expected:

protocol P {}
class X {}

func test() {
    let x = X()
    let y = x is P
}

Cannot downcast from 'X' to non-@objc protocol type 'P'

Conclusion: in order for a protocol typed array to be downcast to a concrete type array, the protocol must be marked with the @objc attribute. The reason is that the runtime uses the is operator to check for protocol conformance, which accordingly to the documentation is only available for bridged protocols.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!