范型可以说是 Swift 跟 OC 相比最大的优势了。通过给像集合这类东西关联泛型, 可以写出更可预测并且更安全的代码。在 Swift4 中类型约束更为强大, 它能够让我们更能够轻而易举的做很多事情。即使是通用代码, 也能充分的利用 Swift 的类型系统。
例1:
首先我们来看看一个简单的例子。比如说给一个数字数字求和。我们可能会些这样的代码:
// 在这段代码中, 我们定义了一个方法, 接受一个 Int 数组作为参数, 在方法内部使用高阶函数 reduce 最后返回这个结果。 func sum(_ numbers: [Int]) -> Int { return numbers.reduce(0, +) } let array = [1,2,3,4,5] sum(array) // 15
使用泛型约束, 我们可以这样来实现这个需求:
// 在这段代码中, 我们给 Array 添加了类型约束的 Extension。当数组的 Element 遵守了 Numeric 协议的时候, Array 就拥有 sum 这个方法。 extension Array where Element: Numeric { func sum() -> Element { return reduce(0, +) } } array.sum() // 15
两者相比, 使用泛型约束最大的优势是使用扩展, 能够让这个功能跟调用者更紧密。比较一下:
let sum = sum(array) let sum = array.sum()
在 OC 里面, 可能有些同学会写一个 XXXTool 之类的类, 来封装这种类型的功能。 或者是直接写成 C 的函数。但是不论怎么写, 这样貌似都不是特别的 OOP。或者OC 还可以直接给 NSArray 加一个 category, 然后再实现相似的功能。但是, 这样做不就等于所有的 array 都具有这个功能了吗?
例2
再来看这样的需求: 计算某个包含字符串集合中有多少个单词。我们可以通过给集合添加一个扩展轻松的完成这件事情。给 Collection 添加一个约束, 限制集合中的 Element 是 String类型:
extension Collection where Element == String { func countWords() -> Int { return reduce(0) { let components = $1.components(separatedBy: .whitespacesAndNewlines) return $0 + components.count } } } let array2 = ["sunny","cloudy","apple orange"] array2.countWords() // 4
还有一个很酷的做法是约束集合类型中的 Element 是 Closure:
extension Sequence where Element == () -> Void { func callAll() { forEach { $0() } } } let closure1 = { print("1") } let closure2 = { print("2") } let array3 = [closure1, closure2] array3.callAll() //1 //2
例3
还有一个很好用的特性是使用协议定义 API 的时候。这几乎是写可测试代码以及功能解耦的最佳实践了。需要注意的是, 在需要灵活使用嵌套类型的时候, 这可能会有点麻烦。
看例子吧!我们经常都想要定义一些通用的 API, 来管理程序中的各种 model。这时候肯定会想要定义一个协议:
protocol ModelManager { associatedtype Model }
现在我们再来定一个查找符合某个条件的方法:传入某个查询条件, 然后返回符合这个条件的模型数组。
protocol ModelManager { associatedtype Model func models(matching query: String) -> [Model] }
这个时候, 这个协议变成了这样。这个样子依然有问题,不够灵活, 而且还有一个很恐怖的问题: 硬编码。接下来我们试着使用范型约束来使用 Swift 的类型系统, 让这个功能更灵活, 并且使用类型系统来解决硬编码的问题。
接下来, 再给 ModelManager 关联两个类型, Query 和 Collection。Query 用来描述查询的条件。他可以是任何东西, 只要能够描述查询条件就可以。当然, 个人认为可能 enum 是最好的选择。Collection用来描述查询结果, 他用来限制返回的结果就是这个管理类的模型。现在这个协议就成这样了:
protocol ModelManager { associatedtype Model associatedtype Collection: Swift.Collection where Collection.Element == Model associatedtype Query func models(matching query: Query) -> Collection }
有了上面的基础, 就可以很方便的实现一些具有查询功能的模型管理类了。比如说我们要用户管理类, 需要通过用户姓名和年龄段来查询符合要求的用户:
// 定义 User 模型 struct User { var name: String var age: Int } // 定义 User 管理类 class UserManager: ModelManager { typealias Model = User // 查询条件, 姓名或者年龄 enum Query { case name(String) case ageRange(Range) } // 查询方法 func models(matching query: Query) -> [User] { // 这里做了几个假的数据 let user1 = User( name: "sunny", age: 25) let user2 = User( name: "lily", age: 18) let user3 = User( name: "michael", age: 30) let users = [user1, user2, user3] switch query { case .name(let name): return users.filter{ $0.name == name } case .ageRange(let range): return users.filter{ range ~= $0.age } } } } let manager = UserManager() manager.models(matching: .name("sunny")) // [{name "sunny", age 25}] manager.models(matching: .ageRange(10 ..< 20)) // [{name "lily", age 18}]
对有些模型来说, 使用字典来作为返回的 Collection可能是更好的方法。下面这个例子是用来通过影片名称和导演名字来筛选电影的例子。返回的结果通过电影分类来做分类。
// 定义电影分类的枚举, 因为要作为字典的 Key 所有需要 Hashable 协议。 // 使用String 类型的枚举只是为了 hashValue enum Genre: String, Hashable { case cartoon = "cartoon" case action = "action" case comedy = "Comedy" var hashValue: Int { return self.rawValue.hashValue } static func ==(lhs: Genre, rhs: Genre) -> Bool { return lhs.rawValue == rhs.rawValue } } // 定义电影模型, 因为要作为字典的 Key 所有需要 Hashable 协议。 // 使用String 类型的枚举只是为了 hashValue struct Movie: Hashable { var name: String var director: String var genre: Genre var hashValue: Int { return Int("/(name.hashValue)" + "/(director.hashValue)")! } static func ==(lhs: Movie, rhs: Movie) -> Bool { return lhs.name == rhs.name && lhs.director == rhs.director && lhs.genre == rhs.genre } } class MovieManager: ModelManager { typealias Model = (key: Genre, value: Movie) enum Query { case name(String) case director(String) } func models(matching query: Query) -> [Genre : Movie] { // 方法跟上个例子差不多, 就不实现了 return [:] } }
使用泛型约束能够很容易的进行面向协议编程(POP)。
作者:CepheusSun,转载请联系作者获得授权。