Downcast from Any to a Protocol

Downcast from Any to a protocol

Update: This has been fixed in Swift 1.2+ (Xcode 6.3+). The Xcode 6.3 beta release notes say:

Dynamic casts (“as!", “as?" and “is”) now work with Swift protocol
types, so long as they have no associated types.


You can only check for protocol conformance (which includes is, as, and as?) with an @objc protocol. Animal is not @objc.

See the Checking for Protocol Conformance section of the Swift book.

NOTE

You can check for protocol conformance only if your protocol is marked
with the @objc attribute

Downcasting on protocols in Swift

Protocol can be adopted by a struct or enum so it don't do class polymorphism, because they can not be inherited. This is also one of the reasons that why associatedtype exists.

Back to your question, you defined a variable with the same name as the parent protocol in the sub-protocol, but the type is different, this is allowed because the protocol is not a concrete type. But this is not a type overload, because the protocol works differently from the class.

In this case, you'd better use generics, which is associatedtype. But if you have to use these three protocols, you only need to declare the sub-protocol.

...

protocol ComponentA: Component {}

protocol ComponentB: Component {}

struct SomeComponentOfTypeA: ComponentA {
var step: Step = StepA.one
}

struct SomeComponentOfTypeB: ComponentB {
var step: Step = StepB.two
}

let a = SomeComponentOfTypeA()

print(a.step)
print(type(of: a))

let b = SomeComponentOfTypeB()

print(b.step)
print(type(of: b))

Output:

one
SomeComponentOfTypeA
two
SomeComponentOfTypeB

Another way is to meet your needs. Limit SomeComponentOfTypeA only allows StepA type.

protocol StepAProtocol { /* body */ }
protocol StepBProtocol { /* body */ }

enum StepA: Int, CaseIterable, StepAProtocol {
case one
case two
case three
}

enum StepB: Int, CaseIterable, StepBProtocol {
case one
case two
case three
}

protocol Component {
associatedtype T
var step: T { get }
}

protocol ComponentA: Component where T: StepAProtocol {}
protocol ComponentB: Component where T: StepBProtocol {}

struct SomeComponentOfTypeA: ComponentA {
var step: StepA = .one
}

struct SomeComponentOfTypeB: ComponentB {
var step: StepB = .two
}

struct SomeComponentOfTypeC: ComponentB {
var step: StepA = .two // error: Type 'StepA' does not conform to protocol 'StepBProtocol'
}

Failing cast in Swift from Any? to protocol

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.

Swift AnyObject - Down casting an array of protocols to [AnyObject]

It's less than perfect, but it works if the protocol and class are both @objc (and the class subclasses NSObject):

@objc protocol Nameable: class {
var name: String { get }
}

@objc class Person: NSObject, Nameable {
var name: String
init(name: String) {
self.name = name
}
}

...

var array: [Nameable] = [personOne, personTwo]
let array2 = array as [AnyObject] // ✓

Downcast Generic AnyObject to Protocol Associated Type Self.Model

What you are trying to do is not possible. This is because of the strong type safety system in Swift.

It looks like you want to restrict the configuration to the associated type Model, but you also want to provide a default configuration. The issue here is that your default configuration has no guarantee that its generic type will be the same as the type of Model.

The Swift type system won't let you pass <AnyObject> as type <Model>, as it has no way to ensure that the object actually IS of the Model type.

When you make something conform to Configurable and declare the type of the Model, you are saying, "My configuration must use the type I've given." The defaultConfiguration can't guarantee that, so it cannot be used in this way.

From looking at your code, you are using this to determine the type of the responseSerializer to use. But, if you are using different Configurable objects, they will all need different responseSerializers, so they can't all use the default configuration.

I've spent a lot of time thinking about this problem, and I don't see any way around it. You are going to have to change your design in some way.

If you move the responseSerializer on to the Configurable protocol, then you could make the Configuration non-generic. In that case, you would be able to create the responseSerializer in a protocol extension on Configurable. If you need to use the responseSerializer with a configuration independently of a Configurable object, then you would have to create a responseSerializer<AnyObject, NSError> wherever it is that you are using it. I'm not familiar with the entire design and intent of your library, so I'm not sure if this will work for what you are trying to achieve.

The one thing I can tell you with certainty is that your design has to change in order to use the defaultConfiguration.

Cannot downcast from ... to a more optional type

You had mistake in if let syntax. Instead of force cast you want to have optional cast. And since you already have this array you don't want get optional array of pokemons

if let pokemonsList = objects as? [Pokemon] {
self.pokemons = pokemonsList
}


Related Topics



Leave a reply



Submit