3

In Swift, I notice that I can upcast an object that conforms to a protocol called, let's say SubProtocol to another protocol called SuperProtocol which is a super protocol of SubProtocol. But I can't do the same with an array of the protocol. Here's the example code that I ran in Playground:

protocol SuperProtocol {
}

protocol SubProtocol: SuperProtocol {
}

class MyObject: SubProtocol {
}

let value1: SubProtocol = MyObject()
let value2: SuperProtocol = value1 // No error here. Upcasting works.

let array1: [SubProtocol] = [MyObject()]
let array2: [SuperProtocol] = array1 // Error here "Cannot convert value of type '[SubProtocol]' to specified type '[SuperProtocol]'"

This seems counter-intuitive, and I'm wondering why it's not allowed.

2 Answers 2

4

The reason has to do with how protocols inherit differently from classes.

Consider first that protocols can have default implementations, for example:

protocol MammalLocomotion {
    func legs() -> Int
}

extension MammalLocomotion {
    func legs () -> Int {
        return 2
    }
}

protocol CowLocomotion : MammalLocomotion {

}

extension CowLocomotion {
    func legs () -> Int {
        return 4
    }
}

Let's make classes that conform to these protocols:

class Mammal : MammalLocomotion {

}

class Cow : Mammal, CowLocomotion {

}

let mammal = Mammal()
let cow = Cow()

Their legs() methods respond as we'd expect:

mammal.legs() // 2
cow.legs() // 4

But now let's cast cow to Mammal:

let cowAsMammal : Mammal = cow

cowAsMammal.legs() // 2

cow had 4 legs, but now it has 2. This is because, with protocols, the currently known type determines which default implementation is used. So casting the array doesn't work — I think the reasoning is that it would be unexpected for an array cast to alter its contained objects' behavior.

Workaround

As you've noted, this won't work:

let farm : [CowLocomotion] = [Cow(), Cow(), Cow()]
let mammalFarm : [MammalLocomotion] = farm // doesn't work

If you want, you can work around this limitation by mapping the array to the protocol you want:

let farm = [Cow(), Cow(), Cow()]

farm.forEach { print($0.legs()) } // prints 4, 4, 4

let mammalFarm = farm.map { $0 as MammalLocomotion }

mammalFarm.forEach { print($0.legs()) } // prints 2, 2, 2

More information on how protocols inherit is available in the Protocol-Oriented Programming in Swift session from this year's WWDC - transcript here.

Sign up to request clarification or add additional context in comments.

Comments

0

Try this code - just checked, works fine

let array2: [SuperProtocol] = array1.map { $0 as SuperProtocol }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.