首页 文章

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

提问于
浏览
2

我无法让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 <Identifiable,Namable>]类型的值转换为预期的参数类型[Namable]

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

2 回答

  • 1

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

    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

    @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会为您推断出类型 .

相关问题