FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871
I\'m having an odd problem where a cast isn\'t working, but the console shows it as the corre
This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue
and is statically typed as Any(?)
. That instance then cannot be cast to a given protocol that it conforms to.
According to Joe Groff in the comments of the bug report you filed:
This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as
_SwiftValue
object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.
A more minimal example would be:
protocol P {}
struct S : P {}
let s = S()
let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.
print(val as? P) // nil
Weirdly enough, first casting to AnyObject
and then casting to the protocol appears to work:
print(val as AnyObject as! P) // S()
So it appears that statically typing it as AnyObject
makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:
The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.
Here's my solution. I didn't want to just make it into a class
(re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.
I went for boxing my protocol into a struct.
public struct BoxedMyProtocol: MyProtocol {
private let boxed: MyProtocol
// Just forward methods in MyProtocol onto the boxed value
public func myProtocolMethod(someInput: String) -> String {
return self.boxed.myProtocolMethod(someInput)
}
}
Now, I just pass around instances of BoxedMyProtocol.
The problem is that the sender
must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:
protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }
Now everything works as you expect, because the sender
can live and breathe coherently in the Objective-C world.
Still not fixed. My favorite and easiest workaround is by far chain casting:
if let instance = sender as AnyObject as? MyProtocol {
}