扩展 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.
又回到了开始。。。
为什么不能为 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 或者其他泛型类型并不是一眨眼就能想到的。但是,通过理解那些简单的概念,就能够创建高度复用并且是强类型推断的函数来实现需求。