Swift - upcasting array of protocol to array of su

2019-02-25 09:31发布

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条回答
戒情不戒烟
2楼-- · 2019-02-25 09:53

Try this code - just checked, works fine

let array2: [SuperProtocol] = array1.map { $0 as SuperProtocol }
查看更多
beautiful°
3楼-- · 2019-02-25 09:55

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.

查看更多
登录 后发表回答