符合多种协议的转换类型作为单一协议

时间:2016-04-27 21:56:21

标签: arrays swift protocols

我无法让Swift理解符合两个协议的对象数组与符合其中一个协议的数组相同。

我们说我有两个协议,Identifiable和Namable:

protocol Identifiable {
    var identifier: Int { get }
}

protocol Namable {
    var name: String { get }
}

两个函数将打印符合这些协议的对象数组的信息:

func printIdentifiers(itemsToPrint: [Identifiable]) {
    for (itemNumber, item) in itemsToPrint.enumerate() {
        print("\(itemNumber): \(item.identifier)")
    }
}

func printNames(itemsToPrint: [Namable]) {
    for (itemNumber, item) in itemsToPrint.enumerate() {
        print("\(itemNumber): \(item.name)")
    }
}

然后是两个符合这些协议的结构:

struct Friend: Identifiable, Namable {
    var identifier: Int
    var name: String
}

struct Dog: Identifiable, Namable {
    var identifier: Int
    var name: String
}

然后说我有一系列符合这两种协议的项目:

let jeff = Friend(identifier: 232314, name: "Jeff")
let fido = Dog(identifier: 45678, name: "Fido")
let identifiableAndNamableItems: [protocol<Identifiable, Namable>] = [jeff, fido]

当我将jeff分配给Namable的变量时,Swift没有问题:

let namableJeff: Namable = jeff //This is fine!

但是当我尝试这样做时它会吓坏:

printNames(identifiableAndNamableItems)
  

无法将[protocol&lt; Identifiable,Namable&gt;]类型的值转换为   预期参数类型[Namable]

知道为什么吗? Swift直观地知道类型protocol<Identifiable, Namable>的变量可以分配给类型为Namable的变量,因为符合两个协议的任何对象必须只符合其中一个协议。但它不明白符合两个协议的项目数组可以分配给符合其中一个协议的项目数组。

2 个答案:

答案 0 :(得分:1)

Swift无法执行完整的集合类型转换(仅适用于某些幕后的自动Objective-C桥接对象,或者在超级和子类元素的集合之间),其中元素在一个可以分配给另一个的意义上是相关联的。您需要明确地帮助编译器显示逐个元素的转换是有效的,例如在调用.map

之前使用printNames操作
printNames(identifiableAndNamableItems.map{ $0 })
    /* 0: Jeff
       1: Fido */

另请注意,您无需全部使用多种协议来查看此行为;对于例如同样明显的以下更简单的例子

protocol Foo { }
struct Bar: Foo {}

let bar = Bar()
let foo: Foo = bar // ok

let barArr: [Bar] = [Bar(), Bar()]
let fooArr: [Foo] = barArr // cannot convert value of type '[Bar]' to specified type '[Foo]'
// let fooArr: [Foo] = barArr.map{ $0 } // OK

答案 1 :(得分:1)

@dfri@matt都提出了为什么这不起作用的重点。它是隐式集合类型转换极为有限的事实的组合,Swift讨厌在大多数情况下使用非具体类型。

我唯一要补充的是,问题的一个稍微更具体的解决方案(双关语意图)而不是使用map来手动转换类型,就是使用类型擦除,如Rob demonstrates in his answer here

这将允许您将非具体类型的protocol<Identifiable, Namable>包装在一个新的具体类型AnyIdentifiableAndNamable中(随意提出一个更吸引人的名字)。然后,您可以将此具体类型用于阵列。

您希望它看起来像这样:

struct AnyIdentifiableAndNamable:Identifiable, Namable {

    // your non-concrete typed base
    private let _base:protocol<Identifiable, Namable>

    // implement protocol properties to simply return the base's property
    var identifier: Int {return _base.identifier}
    var name: String {return _base.name}

    init<T:Identifiable where T:Namable>(_ base:T) {
        _base = base
    }
}

let jeff = Friend(identifier: 232314, name: "Jeff")
let fido = Dog(identifier: 45678, name: "Fido")
let identifiableAndNamableItems = [AnyIdentifiableAndNamable(jeff), AnyIdentifiableAndNamable(fido)]

然后您只需修改打印功能即可使用泛型。例如:

func printIdentifiers<T:Identifiable>(itemsToPrint: [T]) {
    for (itemNumber, item) in itemsToPrint.enumerate() {
        print("\(itemNumber): \(item.identifier)")
    }
}

func printNames<T:Namable>(itemsToPrint: [T]) {
    for (itemNumber, item) in itemsToPrint.enumerate() {
        print("\(itemNumber): \(item.name)")
    }
}

现在,您无需执行任何转换即可将[AnyIdentifiableAndNamable]传递给[T],因为Swift会为您推断出类型。