2016-04-27 97 views
1

我很难让Swift了解符合两种协议的对象数组与符合其中一个的数组相同。符合多种协议的Casting类型作为单个协议

比方说,我有两个协议,可识别并命名的:

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

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

但它怪胎的时候我尝试做:符合这两个协议的项目rray

printNames(identifiableAndNamableItems) 

无法将类型[protocol < Identifiable,Namable>]的值转换为 预期参数类型[Namable]

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

+1

“但它不明白符合两种协议的项目数组可以分配给符合其中一种协议的项目数组”正确,因为“项目数组符合协议“实际上并不是一个完整的类型。请参阅我的问题在这里的讨论:http://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself – matt

回答

1

夫特不能执行完全收集类型转换(仅适用于某些后面引擎罩自动目的-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 
+0

有趣。那么这是一个很好的解决方法,而且编译器的未来版本可能会更聪明。我已经提交了一个雷达,以防万一,这有助于:) –

1

两个@dfri@matt作出关于为什么这不起作用大点。这是隐式集合类型转换极其有限的事实的组合,并且Swift在大多数情况下不喜欢使用非具体类型。

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

这将允许你在一个新的具体类型AnyIdentifiableAndNamable(随意提出一个更吸引人的名字)包装你的非具体类型的protocol<Identifiable, Namable>。然后你可以使用这个具体类型来为你的数组。

你会希望它看起来是这样的:

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将推出其类型为您服务。

+0

有趣的,谢谢一吨的解释! –