16

考虑以下:

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

有人可以解释一下吗?

这个 SO question 导致我遇到了这个问题。

4

2 回答 2

18

斯威夫特 3 更新

从 Swift 3(特别是 Xcode 8 beta 6 附带的构建)开始,集合类型现在可以在后台执行从值类型元素集合到抽象类型元素集合的转换。

这意味着现在将编译以下内容:

protocol SomeProtocol {}
struct Foo : SomeProtocol {}

let arrayOfFoo : [Foo] = []

let arrayOfSomeProtocol : [SomeProtocol] = arrayOfFoo
let arrayOfAny : [Any] = arrayOfFoo

前斯威夫特 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 时,它们可以通过数组隐式完成。

于 2016-05-12T14:48:06.097 回答
1

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

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

于 2016-05-12T14:19:54.523 回答