为什么[SomeStruct]不能转换为[Any]?

时间:2016-05-12 13:35:27

标签: arrays swift swift2.2 swift-structs

请考虑以下事项:

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类型,但这不是问题。我正在寻找解释为什么这样做的原因。

有人可以解释一下吗?

This SO question led me to this problem.

2 个答案:

答案 0 :(得分:17)

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在内存中存储抽象类型的方式。 “存在容器”用于在固定的内存块中存储任意大小的值 - 这意味着对于不能容纳在此容器内的值可能会发生昂贵的堆分配(仅允许对存储器的存储的引用)而不是这个容器。

因此,由于数组现在如何存储在内存中的显着变化,禁止隐式转换是非常合理的。这使程序员明确表示他们必须转换数组的每个元素 - 导致内存结构发生这种(可能是昂贵的)更改。

有关Swift如何使用抽象类型的更多技术细节,请参阅this fantastic WWDC talk on the subject。有关Swift中类型方差的进一步阅读,请参阅此主题的great blog post

最后,请确保查看@dfri's comments below有关数组可以隐式转换元素类型的其他情况 - 即当元素可以桥接到Objective-C时,它们可以由数组隐式地完成。

答案 1 :(得分:1)

Swift无法在包含值类型和引用类型的数组之间自动转换。 只需将数组映射到您需要的类型:

fooArray = barArray.map({$ 0})//编译