扩展 Swift 泛型类型

前言

原文: http://www.marisibrothers.com/2016/03/extending-swift-generic-types.html

Swift 泛型类型,像 Array 或者 Dictionary ,通过使它们关联的类型要么 confirm to Protocol 或者从 class 继承,能够很容易为特定的类型提供新的方法。

在使用 Objective-C 时很容易通过 category 来为 NSArray 添加 convenience methods. 然而,(以前)Objective-C 不是泛型的,所以不能为 NSArray 所含有的特定的类型来添加方法。而 Swift 里 typed arrays 很常见,而且有时我们需要为 typed array 来添加一些有用的方法。这篇文章就探讨一下。 ***

扩展 strings Array(即,Array<String>)

首先,尝试着通过将‘Array 约束成 String’. 然而,Xcode 给出了错误: Same-type requirement makes generic parameter ‘Element’ non-generic. Error

又回到了开始。。。

为什么不能为 associated type non-generic 来扩展呢,因为 Swift 不支持。谨慎猜测,可能是因为和 Swift 自带的 associated type 相冲突。下面的代码可以复现问题:

protocol MyGenericProtocol {
  associatedtype MyGenericProtocolType  //typealias in swift 2.1 and below
}

struct MyGenericStruct<MyGenericStructType>: MyGenericProtocol {
  typealias MyGenericProtocolType = MyGenericStructType
}

extension MyGenericStruct where MyGenericStructType == String {} // compiler error

只能通过 protocol 或者 class 而不是 struct 来为 Array 的特定类型添加 extension. 通过 inheritance 或者 conformance 来约束 associated type 并不完全一致,因为这样做就不能是 associated type (此句是什么鬼??) 很明显, struct 是不能继承的。 试图通过从 struct 来约束 Array,看上去就像下面:

struct MyStruct {}

extension Array where Element: MyStruct {
  // Functions that should only work for an array of MyStruct
}

让人挫败的是这么做会导致 Xcode 编译器错误,但是却没有指出哪一行有错误。。 Xcode 只是笼统地输出错误: Type ‘Element’ constrained to non-protocol type ‘MyStruct’. *** 来说说可能的约束 Array 的方法:

1,让 Element conform to protocol 来约束:

protocol MyProtocol {}

extension Array where Element: MyProtocol {
  // Functions that should only work for an array of MyProtocol
}

2, a, 让 Element 继承自 class 来约束:

class MyClass {}

extension Array where Element: MyClass {
  // Functions that should only work for an array of MyClass
}

2, b, 通过继承自 root final class 的 Element 来约束 Element: 这种方法有效真的很不寻常,尤其是考虑到前面的编译器错误,Same-type requirement makes generic parameter ‘Element’ non-generic. 除非这里错过了什么,通过将 Array 约束成 final class,这意味着 Array 里面的 Element 不再是泛型了。反过来想,上面的(编译器)错误应该是不同类型的数据不能放在同一个 Array 里

final class MyRootFinalClass {}

extension Array where Element: MyRootFinalClass {
  // Functions that should only work for an array of MyRootFinalClass
}

3, 通过 Element 继承自 class 并且 conforms to protocol 来约束:

class MyClass {}

protocol MyProtocol {}

extension Array where Element: MyClass, Element: MyProtocol {
  // Functions that should only work for an array of MyClass objects that conform to MyProtocol
}

举例说明

通过上面的探索,我们可以解决上面的问题了。首先,自定义一个 protocol:StringProtocol 来扩展 Array. 在 StringProtocol 里面声明我们将在 extension 里面用到的函数。最后,我们声明 String conform to StringProtocol.

protocol StringProtocol {
  func hasPrefix(prefix: String) -> Bool
}

extension String : StringProtocol {}

extension Array where Element: StringProtocol {
  
  func filterByPrefix(prefix: String) -> [Element] {
    return filter { (element) -> Bool in
      element.hasPrefix(prefix)
    }
  }
  
}

let strings = ["aa", "ab", "bc"]
strings.filterByPrefix("a") // ["aa", "ab"]

Dictionary example

一个熟悉且有用的场景是为 JSON decoding 来约束 Dictionary. 通过一个简单的 extension,我们可以通过类型推断来将有 String 作为 key 的 JSON 解析成正确的类型数据。举例,从 Dictionary 里将一个 AnyObjects 的数据解析成 String.

protocol StringProtocol {}
extension String : StringProtocol {}

// Error enum for handling missing mandatory keys
enum Error : ErrorType {
  case NoValueForKey(StringProtocol)
}

public protocol DecodingSupportedType {}
extension String : DecodingSupportedType {}

extension Dictionary where Key: StringProtocol {
  
  func decodingKey<ReturnType : DecodingSupportedType>(key: Key) throws -> ReturnType {
    
    guard let value = self[key] as? ReturnType else {
      throw Error.NoValueForKey(key)
    }
    return value
  }
  
  func decodingKey<ReturnType : DecodingSupportedType>(key: Key) -> ReturnType? {
    return self[key] as? ReturnType
  }
}

这个简单的 Dictionary extension 能够很容易地去 decode [String: AnyObject], 并且保证类型安全:

let dictionary: [String: AnyObject] = ["key": "value"]

/// Type inference from variable type
let mandatoryValue: String = try! dictionary.decodingKey("key") //"value\n"
let optionalValue: String? = try? dictionary.decodingKey("key") //"Optional("value")\n"

// Handling failures with mandatory keys
do {
  let mandatoryValue: String = try dictionary.decodingKey("otherkey")
} catch let error {
  print(error) // "NoValueForKey("otherkey")\n"
}

结论

怎样去 extend Array Dictionary 或者其他泛型类型并不是一眨眼就能想到的。但是,通过理解那些简单的概念,就能够创建高度复用并且是强类型推断的函数来实现需求。

Loading Disqus comments...
Table of Contents