一尘不染

向扩展中的通用参数添加约束

swift

我有这个功能:

func flatten<Key: Hashable, Value>(dict: Dictionary<Key, Optional<Value>>) -> Dictionary<Key, Value> {
    var result = [Key: Value]()
    for (key, value) in dict {
        guard let value = value else { continue }
        result[key] = value
    }
    return result
}

如您所见,它将[Key: Value?]字典转换成一个字典[Key: Value](没有可选的)。

我只想为Dictionary值是Optional任何类型的类使用新方法扩展该类,但是我无法为字典的通用参数添加约束。

这是我尝试的:

extension Dictionary where Value: Optional<Any> {
    func flatten() -> [Key: Any] {
        var result = [Key: Any]()
        for (key, value) in self {
            guard let value = value else { continue }
            result[key] = value
        }
        return result
    }
}

但失败并显示错误:

Type 'Value' constrained to non-protocol type 'Optional<Any>'

阅读 281

收藏
2020-07-07

共1个答案

一尘不染

在Playground中尝试以下代码:

// make sure only `Optional` conforms to this protocol
protocol OptionalEquivalent {
  typealias WrappedValueType
  func toOptional() -> WrappedValueType?
}

extension Optional: OptionalEquivalent {
  typealias WrappedValueType = Wrapped

  // just to cast `Optional<Wrapped>` to `Wrapped?`
  func toOptional() -> WrappedValueType? {
    return self
  }
}

extension Dictionary where Value: OptionalEquivalent {
  func flatten() -> Dictionary<Key, Value.WrappedValueType> {
    var result = Dictionary<Key, Value.WrappedValueType>()
    for (key, value) in self {
      guard let value = value.toOptional() else { continue }
      result[key] = value
    }
    return result
  }
}

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil]
a.flatten() //["a": "a", "c": "c"]

因为您不能在where协议扩展的子句中指定确切的类型,所以可以准确检测到该Optional类型的一种方法是使OptionalUNIQUELY符合协议(例如OptionalEquivalent)。

为了获得的包装值类型Optional,我WrappedValueType在自定义协议中定义了typealias
OptionalEquivalent,然后对Optional进行了扩展,将assgin设置WrappedWrappedValueType,然后可以在flatten方法中获取类型。

注意,该sugarCast方法只是将to强制转换Optional<Wrapped>Wrapped?(这是完全相同的),以启用用法guard语句。

更新

感谢Rob Napier的评论,我简化并重命名了sugarCast()方法,并重命名了协议以使其更易于理解。

2020-07-07