15
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?

reference: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_498


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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ken Zhang
  • 1,454
  • 2
  • 13
  • 27
  • I've updated my answer - the previous one was clearly wrong. – Antonio Sep 01 '14 at 08:25
  • I still don't understand why the `is` and `as?` operator works for a single variable, but not for an array. But this can be solved by `flagMap` now. – Ken Zhang Sep 05 '15 at 07:12

1 Answers1

16

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.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • (With regard to your comments to the now deleted question http://stackoverflow.com/questions/25589605/swift-forced-upwrapping-of-array-of-optionals: It is perfectly OK to answer your own question, and people are encouraged to share their knowledge by doing so. See http://stackoverflow.com/help/self-answer for more information.) – Martin R Aug 31 '14 at 07:29
  • Thanks @MartinR - I've always seen people posting comments like mine, so I thought it was not allowed. Did a (probably too much quick) search and couldn't find anything explicitly saying it's allowed or discouraged. I'l try to reach him with my apoligies... – Antonio Aug 31 '14 at 07:48
  • This is wrong. If `A` is a subclass of `B`, `[A]` is implicitly convertible to `[B]`, and `[B]` can be explicitly cast to `[A]`. Neither `Int` nor `UInt` are subclasses of one another (in fact, neither of them are classes, let alone subclasses). – newacct Sep 01 '14 at 06:14
  • Wow...what a big mistake I did. You're right @newacct, thanks for pointing that out – Antonio Sep 01 '14 at 06:25
  • I've updated the answer with the "why" part that was still missing – Antonio Sep 05 '14 at 10:22
  • Thanks. This solved my crash when casting in the other direction (`[ConformerOfProtocol] as [Protocol]`) – Andreas Jan 07 '15 at 18:04
  • @Antonio: Is there any update on this as of Swift 2? I am still getting the 'cannot be bridged' runtime error. Your workaround does work, but I have no intention of adding `@objc` to my protocols when it clearly makes no sense. – Guven Jul 11 '15 at 15:29
  • @Guven: The `is` operator no longer requires the type to be `objc` in order to be used. Also, the code in the question seem to work in 2.0. – Antonio Jul 12 '15 at 06:56
  • How does Apple think we can do protocol-oriented program when we can't do this??? Good theory but not practical :( – TruMan1 Apr 02 '16 at 23:09
  • Sadly this still doesn't work with Swift 2.2: http://stackoverflow.com/questions/36379934/fatal-error-when-casting-array-of-types-to-protocols-cannot-be-bridged-from-obj – TruMan1 Apr 02 '16 at 23:36