Ответ 1
Обновление Swift 3
Начиная с Swift 3 (в частности, сборки, которая поставляется с Xcode 8 beta 6), типы коллекций теперь могут выполняться под преобразованиями капюшона из наборов элементов с типизированной типизацией в коллекции элементов с абстрактным набором.
Это означает, что теперь будет скомпилировано следующее:
protocol SomeProtocol {}
struct Foo : SomeProtocol {}
let arrayOfFoo : [Foo] = []
let arrayOfSomeProtocol : [SomeProtocol] = arrayOfFoo
let arrayOfAny : [Any] = arrayOfFoo
Pre Swift 3
Все это начинается с того, что дженерики в Свифте инвариантны, а не ковариантны. Помня, что [Type]
является просто синтаксическим сахаром для Array<Type>
, вы можете абстрагироваться от массивов и Any
, чтобы, надеюсь, лучше видеть проблему.
protocol Foo {}
struct Bar : Foo {}
struct Container<T> {}
var f = Container<Foo>()
var b = Container<Bar>()
f = b // error: cannot assign value of type 'Container<Bar>' to type 'Container<Foo>'
Аналогично классам:
class Foo {}
class Bar : Foo {}
class Container<T> {}
var f = Container<Foo>()
var b = Container<Bar>()
f = b // error: cannot assign value of type 'Container<Bar>' to type 'Container<Foo>'
Этот вид ковариантного поведения (повышение) просто невозможен с помощью дженериков в Свифте. В вашем примере Array<SomeStruct>
рассматривается как совершенно не связанный тип с Array<Any>
из-за инвариантности.
Тем не менее, массивы имеют исключение из этого правила - они могут без проблем обрабатывать преобразования из типов подклассов в типы суперкласса под капотом. Тем не менее, они не делают то же самое при преобразовании массива со значениями типизированных элементов в массив с абстрактно-типизированными элементами (например, [Any]
).
Чтобы справиться с этим, вы должны выполнить собственное поэтапное преобразование (поскольку отдельные элементы ковариантны). Общим способом достижения этого является использование map(_:)
:
var fooArray : [Any] = []
let barArray : [SomeStruct] = []
// the 'as Any' isn't technically necessary as Swift can infer it,
// but it shows what happening here
fooArray = barArray.map {$0 as Any}
Хорошая причина предотвратить неявное преобразование "под капотом" объясняется тем, как Swift сохраняет абстрактные типы в памяти. "Экзистенциальный контейнер" используется для хранения значений произвольного размера в фиксированном блоке памяти, а это означает, что для значений, которые не могут быть помещены в этот контейнер, может возникнуть дорогое распределение кучи (позволяющее просто ссылаться на память, которая будет храниться в этот контейнер вместо этого).
Поэтому из-за этого значительного изменения в том, как массив теперь хранится в памяти, вполне разумно запретить неявное преобразование. Это делает явным для программиста, что им приходится бросать каждый элемент массива, вызывая это (потенциально дорогое) изменение структуры памяти.
Подробнее о том, как Swift работает с абстрактными типами, см. этот фантастический разговор WWDC по этому вопросу. Для дальнейшего чтения о дисперсии типа в Swift см. Этот отличный пост в блоге по этому вопросу.
Наконец, не забудьте увидеть @dfri комментарии ниже о другой ситуации, когда массивы могут неявно преобразовывать типы элементов, а именно когда элементы мостифицируются до Objective-C, они могут быть сделаны неявно с помощью массива.