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

Laf*_*fen 16 arrays swift swift-structs swift2.2

考虑以下:

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]'
Run Code Online (Sandbox Code Playgroud)

我一直试图找到这背后的逻辑,但没有运气.值得一提的是,如果将结构更改为类,则可以完美地运行.

总是可以添加一个变通方法并映射fooArray的每个对象并将它们转换为Any类型,但这不是问题.我正在寻找一个解释为什么这样做的样子.

有人可以解释一下吗?

这个问题让我想到了这个问题.

Ham*_*ish 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
Run Code Online (Sandbox Code Playgroud)

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>'
Run Code Online (Sandbox Code Playgroud)

与课程类似:

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>'
Run Code Online (Sandbox Code Playgroud)

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} 
Run Code Online (Sandbox Code Playgroud)

防止隐含的"引擎盖下"转换的一个很好的理由是由于Swift在内存中存储抽象类型的方式."存在容器"用于在固定的内存块中存储任意大小的值 - 这意味着对于不能容纳在此容器内的值可能会发生昂贵的堆分配(仅允许对存储器的存储的引用)这个容器代替).

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

有关Swift如何使用抽象类型的更多技术细节,请参阅这个关于此主题的精彩WWDC演讲.有关Swift中类型差异的进一步阅读,请参阅此主题的精彩博客文章.

最后,请确保在下面看到@dfri的注释,其中有关数组可以隐式转换元素类型的其他情况 - 即当元素可以桥接到Objective-C时,它们可以由数组隐式地完成.

  • 既然你提到了从子类类型(和实例)数组到超类类型数组的转换异常,我可能会添加一个额外的异常:符合内部协议`_ObjectiveCBridgeable`的元素数组(例如`Int`,隐式桥接到`NSNumber`类型,`UInt`,`Double`,`String`等等)可以直接赋值给元素类型`AnyObject`(`Array <AnyObject>`)的数组,在引擎盖后面使用member-per-member隐式桥接从本机Swift类型到相应Cocoa数据类型的转换. (2认同)