Подтвердить что ты не робот

Почему [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, но это не проблема. Я ищу объяснение, почему это так себя ведет.

Может кто-нибудь объяснить это?

Этот вопрос помог мне решить эту проблему.

4b9b3361

Ответ 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, они могут быть сделаны неявно с помощью массива.

Ответ 2

Swift не может автоматически конвертировать между массивом, который содержит типы значений и ссылочные типы. Просто сопоставьте массив с типом, который вам нужен:

fooArray = barArray.map({$ 0})//Компилирует