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.