在 Swift 中使用协议作为类型时出错

Posted

技术标签:

【中文标题】在 Swift 中使用协议作为类型时出错【英文标题】:Error When Using Protocol as a Type in Swift 【发布时间】:2016-01-19 17:50:01 【问题描述】:

我正在使用 Swift 并尝试制作一些集合对象。这些集合对象有一个支持Dictionary 来保存自定义对象。例如,一个对象的类型可能是Cat,而集合对象的类型可能是CatsCats 将有一个包含 Cat 类型值的私有字典。我还有其他类型也需要各自的集合(每个集合类型都有它所拥有的类型的特定逻辑)。

我创建了一个协议来确保每个集合都有一些共同特征。这些常用函数和下标通常是对支持字典的传递。这是协议:

protocol ObjectDictionaryProtocol 

    // These are necessary for generics to work in protocols
    typealias Key: Hashable
    typealias Value


    // MARK: - Properties

    var count: Int  get 
    var isEmpty: Bool  get 
    var keys: LazyMapCollection<Dictionary<Key, Value>, Key>  get 
    var values: LazyMapCollection<Dictionary<Key, Value>, Value>  get 


    // MARK: - Subscripts

    subscript(key: Key) -> Value?  get set 


当我去实际使用协议作为类型时,例如:

var objects: ObjectDictionaryProtocol

init(objs: ObjectDictionaryProtocol) 
    ...

我得到错误:

Protocol 'ObjectDictionaryProtocol' can only be used as a generic constraint because it has Self or associated type requirements

我四处搜索,看起来我的Key typealias 遵循的Hashable 协议导致了这种情况。解决这个问题的最佳方法是什么?有没有办法更改协议以使我不需要Hashable,或者我是否需要在使用ObjectDictionaryProtocol 的类中做一些事情?或者也许有更好的方法来有效地“子类化”Swift Dictionary(引用是因为我意识到 Dictionary struct 不能被子类化)?

【问题讨论】:

实际上,如果你把所有的东西都去掉,你会发现仅仅在你的协议中使用一个类型别名就禁止你使用协议作为一种类型,正如错误消息中所解释的那样:"。 .. 或相关的类型要求”。如果包含类型别名,则包含关联类型。我建议您使用符合您协议的自定义结构。 【参考方案1】:

具有关联类型的协议与其说是一种类型,不如说是一种类型的模板。当指定协议的关联类型时,实际类型存在。因此,ObjectDictionaryProtocol 在您指定时“成为”一种类型:

ObjectDictionaryProtocol<String,Cat>

但上面的 Swift 无效...

所以你问...'[是否有]一种更好的方法来有效地'子类'一个 Swift 字典'。你可能会得到一个extension 类似的东西:

class Cat 

extension Dictionary where Value : Cat 
  func voice (name: Key) 
    if let _ = self[name] 
      print ("\(name): Meow")
    
  


var someCats = Dictionary<String,Cat>()
someCats["Spot"] = Cat()
someCats.voice("Spot")
// Spot: Meow

或者您可能需要实际实现该协议。

class ObjectDiciontary<Key:Hashable, Value> : ObjectDictionaryProtocol 
  var backingDictionary = Dictionary<Key,Value>()
  // implement protocol


var object : ObjectDictionary<String,Cat> = ...
// ...

【讨论】:

【参考方案2】:

原因是因为当您使用typealias 时,您实际上将您的Protocol 变成了Protocol&lt;T&gt; 的通用协议。在这里忍受我,我将解释为什么以及如何解决它。

这里的问题是 Apple 决定将 typealias 用作定义关联类型的关键字。这在 Swift 2.2 (Xcode 7.3) 中已修复 你可以在https://github.com/apple/swift-evolution/blob/master/proposals/0011-replace-typealias-associated.md阅读更多相关信息

它被重命名为associatedtype,这更有意义。

这意味着必须采用您的协议,并在其中定义其关联类型。

在你的情况下,它看起来像

protocol ObjectDictionaryProtocol 
  associatedtype Value


extension String : ObjectDictionaryProtocol 
  associatedtype = Double

初始化可能看起来像

 init<T : ObjectDictionaryProtocol>(objs: ObjectDictionaryProtocol)

init<T : ObjectDictionaryProtocol 
     where T.Value == Double>(objs: ObjectDictionaryProtocol)

现在对于typealias Key: Hashable,这意味着分配给Key 的任何类型都必须符合或为Hashable

这会给你一个错误,String 不符合 ObjectDictionaryProtocol 因为它不能满足要求。

protocol ObjectDictionaryProtocol 
  associatedtype Value : FloatingPointType


extension String : ObjectDictionaryProtocol 
  associatedtype = Int

Int 不是 FloatingPointType(但 Double 是)

【讨论】:

以上是关于在 Swift 中使用协议作为类型时出错的主要内容,如果未能解决你的问题,请参考以下文章

Swift 2在协议扩展中使用变异函数时出错“无法在不可变值上使用变异成员:'self'是不可变的

Apollo graphQL:在 iOS swift 中使用自定义标量时解码数组时出错

在 Mongoose Schema 中使用 _id 作为属性类型时出错

Swift:尝试导入 UIKit 时出错

在 swift 中使用 valueForKey 时出错

在存储过程中使用日期时间作为参数更新表时出错