protocol P : class {
var value:Int {get}
}
class X : P {
var value = 0
init(_ value:Int) {
self.value = value
}
}
var ps:[P] = [X(1), X(2)]
for p in ps {
if let x = p as? X { // works for a single variable
...
}
}
if let xs = ps as? [X] { // doesn't work for an array (EXC_BAD_ACCESS)
...
}
If P is a class instead of a protocol, than the code works correctly. What's the difference between class and protocol? They're both implemented as pointers in the heap, aren't they? The above code can be compiled successfully, but crash at runtime. What does this EXC_BAD_ACCESS error mean?
Thanks to @Antonio, but I still don't understand how this sample code works.
let someObjects: [AnyObject] = [
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]
for movie in someObjects as [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Is AnyObject a special case?
protocol P {
}
@objc class X : P {
}
@objc class Y : X {
}
var xs:[X] = [Y(), Y()]
var ps:[P] = [Y(), Y()]
xs as? [Y] // works
ps as? [Y] // EXC_BAD_ACCESS
I tried this code in playground. Since this is pure swift code, I think it has nothing to do with @objc.
Ignoring the optional binding for a moment and using a direct assignment:
the following runtime error is reported:
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:
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:
We create an array of P:
Converting the empty array to
[X]
works:If we add an element to the array, a runtime error occurs:
The console output says that:
So casting an array of protocol objects to an array of its adopter requires bridging. That justifies why @objc fixes the issue:
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 theP
protocol. The documentation clearly states that:To verify that (not that I don't trust the documentation), I've used this code in the playground:
but I get a different error, stating that:
Writing that in a "regular" project instead we get what expected:
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 theis
operator to check for protocol conformance, which accordingly to the documentation is only available for bridged protocols.