Почему [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}) // Компилируется

Другие вопросы по тегам