Почему [SomeStruct] не конвертируется в [Any]?
Учтите следующее:
struct SomeStruct {}
var foo: Any!
let bar: SomeStruct = SomeStruct()
foo = bar // Compiles as expected
var fooArray: [Any] = []
let barArray: [SomeStruct] = []
fooArray = barArray // Does not compile; Cannot assign value of type '[SomeStruct]' to type '[Any]'
Я пытался найти логику, но безуспешно. Стоит упомянуть, если вы измените структуру на класс, она работает отлично.
Можно всегда добавить обходной путь и отобразить каждый объект fooArray и привести их к типу Any, но здесь проблема не в этом. Я ищу объяснение, почему это ведет себя так, как есть.
Может кто-нибудь, пожалуйста, объясните это?
2 ответа
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
Все это начинается с того, что дженерики в Swift инвариантны, а не ковариантны. Вспоминая это [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>'
Этот вид ковариантного поведения (апкастинга) просто невозможен с обобщениями в Swift. В вашем примере 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's happening here
fooArray = barArray.map {$0 as Any}
Хорошая причина предотвратить неявное преобразование "изнутри" здесь связано с тем, как Swift хранит абстрактные типы в памяти. "Existential Container" используется для хранения значений произвольного размера в фиксированном блоке памяти - это означает, что дорогостоящее выделение кучи может происходить для значений, которые не могут поместиться в этом контейнере (позволяя просто сохранить ссылку на память в этот контейнер вместо).
Поэтому из-за этого существенного изменения в том, как массив теперь хранится в памяти, вполне разумно запретить неявное преобразование. Это дает понять программисту, что он должен приводить каждый элемент массива - вызывая это (потенциально дорогое) изменение в структуре памяти.
Для получения дополнительной технической информации о том, как Swift работает с абстрактными типами, см. Этот фантастический доклад WWDC на эту тему. Для дальнейшего чтения о типовой дисперсии в Swift, см. Этот отличный пост в блоге на эту тему.
Наконец, обязательно посмотрите комментарии @dfri ниже о другой ситуации, когда массивы могут неявно преобразовывать типы элементов - а именно, когда элементы могут быть соединены с Objective-C, они могут быть сделаны неявно с помощью массива.
Swift не может автоматически конвертировать массив, который содержит типы значений и ссылочные типы. Просто сопоставьте массив с нужным вам типом:
fooArray = barArray.map ({$ 0}) // Компилируется