92

I have declared a Swift protocol:

protocol Option {
    var name: String { get }
}

I declare multiple implementations of this protocol—some classes, some enums.

I have a view controller with a property declared as so:

var options: [Option] = []

When I try and set this property to an array of objects that implement the Option protocol in another VC's prepareForSegue, I get a runtime error:

fatal error: array cannot be bridged from Objective-C

Why doesn't this work? The compiler has all the information it needs, and I don't understand what Objective-C has to do with it at all—my project contains only Swift files, and these arrays aren't coming in or out of any framework methods that would necessitate them being bridged to NSArray.

Robert Atkins
  • 23,528
  • 15
  • 68
  • 97
  • 6
    Did you try to prepend `@objc` to your protocol? http://stackoverflow.com/a/28029568/377369 – Fabio Poloni May 07 '15 at 12:18
  • 1
    That doesn't work if any of the protocol implementations is an enum: "Non-class type 'Foo' cannot conform to class protocol 'Option'" – Robert Atkins May 07 '15 at 12:21
  • Why must it be a class protocol though? I'm not passing it to an Obj-C framework or anything else that requires the Swift Array to be bridged to NSArray. – Robert Atkins May 07 '15 at 12:24
  • They way Swift and Objective-C work together is still a secret to me. I just have to "accept" many things which just "work" or "don't work". – Fabio Poloni May 07 '15 at 12:30
  • 9
    Why does this one have so many downvotes? Looks like a fair and clear question to me. – Guven Jul 11 '15 at 06:51
  • Note that I'm not the only one having issues with this: http://kickingbear.com/blog/archives/521 – Robert Atkins Aug 14 '15 at 09:57
  • I got this entirely in Swift code (though in a class derived from NSObject), when passing from a function taking an array of a concrete type to an function taking an array of optionals of that same type. This feels like a bug in the compiler's static analysis in the area of array covariance. I fixed it by making sure there was no difference in optionality of the array element types. – Chris Conover Oct 10 '16 at 22:59
  • For the record, I've finally gotten around to updating the project concerned to Swift 3.0(.1) and it seems the problem has gone away. – Robert Atkins Dec 04 '16 at 15:33

4 Answers4

83

I have found a solution. It is quite... unsatisfying, but it works. Where I set the array on the destination view controller I do:

destinationViewController.options = options.map({$0 as Option})
Robert Atkins
  • 23,528
  • 15
  • 68
  • 97
22

the compiler knows I'm passing in an Array of things that implement Option

You've let slip there a very revealing remark, which suggests the source of the issue. An "Array of things that implement Option" is not an Array of Option.

The problem is with the type of options back at the point where you create it (in prepareForSegue). You don't show that code, but I am betting that you fail to cast / type it at that point. That's why the assignment fails. options may be an array of things that do in fact happen to adopt Option, but that's not enough; it must be typed as an array of Option.

So, back in prepareForSegue, form your options like this:

let options : [Option] = // ... whatever ...

Now you will be able to assign it directly to destinationViewController.options.

Here's a quick test case (in a playground; I detest playgrounds, but they can have their uses):

protocol Option {
    var name : String {get}
}

class ViewController : UIViewController {
    var options : [Option] = []
}

enum Thing : Option {
    var name : String {
        get {
            return "hi"
        }
    }
    case Thing
}

let vc = ViewController()
let options : [Option] = [Thing.Thing]
vc.options = options // no problem

(I also tested this in an actual app with an actual prepareForSegue, and it works fine.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    I think this is broken in the extreme because the compiler *does* know at runtime that Thing is an Option. And in any case, as noted in the comment to my own answer below, neither casting (`viewController.options = things as [Option]`) nor creating a temp variable explicitly typed as `[Option]` as you suggest here, actually works. In both cases I get the runtime error. – Robert Atkins May 07 '15 at 12:50
  • Then you have to explain why it works for me. Something else is going on that you have not stated. If you do not reveal more code, I simply have to suspect that you are holding back something essential. – matt May 07 '15 at 12:56
  • Maybe. But I'm still confused as to what this has to do with Objective-C in the first place (vis. the original runtime error.) I'm not doing anything (that I can see) which ought to force a bridging cast to NSArray. – Robert Atkins May 07 '15 at 13:04
  • I might be able to say more about that if I could see more of your of code. – matt May 07 '15 at 13:05
  • By definition, I don't know which parts to show you because I don't know what parts are relevant. – Robert Atkins May 07 '15 at 13:19
  • 2
    Look at it this way. I've shown you code that works. You have _not_ shown me code that _doesn't_ work - I cannot reproduce your issue from the data given. Help me reproduce it. – matt May 07 '15 at 13:28
  • This is the correct answer to the original problem. – Peter Segerblom Dec 08 '15 at 14:14
  • This should be marked as the correct answer. The message thrown by Xcode should be considered a bug, though (and a better message should be displayed by Xcode). – diegoreymendez Feb 25 '16 at 00:08
  • I think the code in this answer only works because the right hand side of the assignment `let options: [Option] = [Thing.Thing]` is a literal. If it had been an array of type `[Thing]`, it would not have worked. Maybe this is the problem the OP ran into. Personally, I have solved the problem for me through generic functions that use the protocol as a type constraint. – Thorsten Karrer Apr 07 '16 at 14:09
  • This issue is still not fixed, the discussion is insane, never saw a language that can't handle it. Isn't this what protocols are for, to use it instead the concrete class? This would mean you also can't assign an object that was declared as a protocol – Cristi Băluță Aug 22 '16 at 13:59
  • No, i'm still on the stable version, is it fixed in swift3? – Cristi Băluță Aug 23 '16 at 07:25
  • 1
    @CristiBăluță That is what you would need to find out before claiming "this issue is still not fixed" – matt Aug 23 '16 at 13:46
  • Seems to be fixed with Swift 3.0.1 for my original case. – Robert Atkins Dec 04 '16 at 15:35
  • @RobertAtkins Yes, there's a whole new way of bridging arrays to Objective-C, at last. – matt Dec 04 '16 at 15:54
16

I was having the same problem and fixed it marking my protocol with @objc, in your case it would look like this

@objc protocol Option {
    var name: String { get }
}

Got the solution from this answer

Community
  • 1
  • 1
Juan
  • 404
  • 3
  • 13
1

This one also works fine

destinationViewController.options = options.map{$0}
Mykola Denysyuk
  • 1,935
  • 1
  • 15
  • 15