3

I'm having trouble with some Swift Optional Binding with a cast into a protocol. I have the following code in a playground that works fine.

protocol CodeCollection {
    var name: String { get }
    var codes: [String] { get }
}

struct VirtualDoors: CodeCollection {
    var name = "Virtual Doors"
    var codes: [String] = ["doorNumba1", "doorNumba2"]
}

// Instance of VirtualDoors
let doors = VirtualDoors()

// cast into Any? like what awake(withContext context: Any?) receives
var context = doors as Any?

print(context)
if let newDoors = context as? CodeCollection {
    // Works as expected
    print(newDoors)
}

I'm using the exact same protocol and struct in watchKit as a piece of info passed in awake(withContext context: Any?) and the optional binding with cast is failing there.

override func awake(withContext context: Any?) {
    super.awake(withContext: context)

    // Just checking to make sure the expected item is in fact being passed in
    print(context)
    // "Optional(VirtualDoors(name: "Virtual Doors", codes: ["doorNumba1", "doorNumba2"]))\n"

    if let newDoors = context as? CodeCollection {
        self.collection = newDoors
        print("Context Casting Success")
    } else {
        // Casting always fails
        print("Context Casting Fail")
    }
}

I'd be really appreciative if someone could tell me why this is working in the playground but not in the watchKit class method.

I feel like I am missing something really obvious.

vichudson1
  • 881
  • 1
  • 10
  • 21
  • Do you have two different definitions of CodeCollection with different namespaces? – Mike Taverne Sep 20 '16 at 03:51
  • I just have the one. Only the protocol has that name. I've been experimenting and if I try casting to the struct VirtualDoor type, it works fine, of course then I couldn't use this view to display other types of collections. – vichudson1 Sep 20 '16 at 04:03
  • Actually maybe that is something. The protocol is defined in a folder outside the watch kit extension in a "Shared" folder. I do have both the iOS app and watchKit Target boxes checked for it though. Aside from that I'm not sure what you mean or how to fix it. First foray into making an app to share code between targets. Any advice? – vichudson1 Sep 20 '16 at 04:08
  • If it's included in both targets, then I think you will have two different classes with identical names but in different namespaces. Is it possible to remove it from one of the targets? – Mike Taverne Sep 20 '16 at 04:20
  • Yeah I can do that. It's not really doing anything in iOS yet. I should point out that I was having this issue before I added it to both targets, it was originally only on the watch kit target. – vichudson1 Sep 20 '16 at 04:26
  • Okay @MikeTaverne so I tried renaming the protocol and struct and placing them in a file that only exists in the WatchKit extension target and still no joy. I don't think it's a namespace issue. – vichudson1 Sep 20 '16 at 14:35
  • @vichudson I believe context must be a reference type, not a value type. Unfortunately, I cannot find any evidence of this in the docs. But, I created a similar project and switched VirtualDoors to be a class and it worked. – DeepFriedTwinkie Sep 20 '16 at 15:02
  • That may be what I end up doing, but it smells. This has to be a bug I think. Or they should document that context needs to be a reference type. – vichudson1 Sep 20 '16 at 17:02

1 Answers1

1

I suspect you doors to context as an implicit Any??, which only unwraps to another Optional instead of CodeCollection.

If you use let context = context as AnyObject inside the awake function, then should be able to unwrap it correctly.

Think of it like an force-unwraped optional that you don't get to see.

The last two comments of this playground should give others an example to play with where the optionals are kept, but the optional type is erased and wrapped.

import Foundation

protocol Target {
    var name: String { get }
}

func takesAnyOptional(context: Any?) -> String? {
    return (context as? Target)?.name
}

struct Source: Target {
    let name = "Source"
}

let solid = Source()
print((solid as Target).name)
takesAnyOptional(context: solid)

let solid_any = solid as Any
print((solid_any as? Target)?.name)
takesAnyOptional(context: solid_any)
takesAnyOptional(context: solid_any as Any?)

let solid_anyOpt = solid as Any?
print((solid_anyOpt as? Target)?.name)
takesAnyOptional(context: solid_anyOpt)
takesAnyOptional(context: solid_anyOpt as Any) // -> double optional -> nil

let solid_anyOpt_any = solid_anyOpt as Any
takesAnyOptional(context: solid_anyOpt_any) // -> double optional -> nil
AgentK
  • 665
  • 7
  • 8