
范型可以說是 Swift 跟 OC 相比最大的優(yōu)勢了。通過給像集合這類東西關聯(lián)泛型, 可以寫出更可預測并且更安全的代碼。在 Swift4 中類型約束更為強大, 它能夠讓我們更能夠輕而易舉的做很多事情。即使是通用代碼, 也能充分的利用 Swift 的類型系統(tǒng)。
例1:
首先我們來看看一個簡單的例子。比如說給一個數(shù)字數(shù)字求和。我們可能會些這樣的代碼:
// 在這段代碼中, 我們定義了一個方法, 接受一個 Int 數(shù)組作為參數(shù), 在方法內部使用高階函數(shù) reduce 最后返回這個結果。
func sum(_ numbers: [Int]) -> Int {
return numbers.reduce(0, +)
}
let array = [1,2,3,4,5]
sum(array) // 15
使用泛型約束, 我們可以這樣來實現(xiàn)這個需求:
// 在這段代碼中, 我們給 Array 添加了類型約束的 Extension。當數(shù)組的 Element 遵守了 Numeric 協(xié)議的時候, Array 就擁有 sum 這個方法。
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
array.sum() // 15
兩者相比, 使用泛型約束最大的優(yōu)勢是使用擴展, 能夠讓這個功能跟調用者更緊密。比較一下:
let sum = sum(array)
let sum = array.sum()
在 OC 里面, 可能有些同學會寫一個 XXXTool 之類的類, 來封裝這種類型的功能。 或者是直接寫成 C 的函數(shù)。但是不論怎么寫, 這樣貌似都不是特別的 OOP?;蛘逴C 還可以直接給 NSArray 加一個 category, 然后再實現(xiàn)相似的功能。但是, 這樣做不就等于所有的 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
還有一個很好用的特性是使用協(xié)議定義 API 的時候。這幾乎是寫可測試代碼以及功能解耦的最佳實踐了。需要注意的是, 在需要靈活使用嵌套類型的時候, 這可能會有點麻煩。
看例子吧!我們經常都想要定義一些通用的 API, 來管理程序中的各種 model。這時候肯定會想要定義一個協(xié)議:
protocol ModelManager {
associatedtype Model
}
現(xiàn)在我們再來定一個查找符合某個條件的方法:傳入某個查詢條件, 然后返回符合這個條件的模型數(shù)組。
protocol ModelManager {
associatedtype Model
func models(matching query: String) -> [Model]
}
這個時候, 這個協(xié)議變成了這樣。這個樣子依然有問題,不夠靈活, 而且還有一個很恐怖的問題: 硬編碼。接下來我們試著使用范型約束來使用 Swift 的類型系統(tǒng), 讓這個功能更靈活, 并且使用類型系統(tǒng)來解決硬編碼的問題。
接下來, 再給 ModelManager 關聯(lián)兩個類型, Query 和 Collection。Query 用來描述查詢的條件。他可以是任何東西, 只要能夠描述查詢條件就可以。當然, 個人認為可能 enum 是最好的選擇。Collection用來描述查詢結果, 他用來限制返回的結果就是這個管理類的模型。現(xiàn)在這個協(xié)議就成這樣了:
protocol ModelManager {
associatedtype Model
associatedtype Collection: Swift.Collection where Collection.Element == Model
associatedtype Query
func models(matching query: Query) -> Collection
}
有了上面的基礎, 就可以很方便的實現(xiàn)一些具有查詢功能的模型管理類了。比如說我們要用戶管理類, 需要通過用戶姓名和年齡段來查詢符合要求的用戶:
// 定義 User 模型
struct User {
var name: String
var age: Int
}
// 定義 User 管理類
class UserManager: ModelManager {
typealias Model = User
// 查詢條件, 姓名或者年齡
enum Query {
case name(String)
case ageRange(Range<Int>)
}
// 查詢方法
func models(matching query: Query) -> [User] {
// 這里做了幾個假的數(shù)據(jù)
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 協(xié)議。
// 使用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 協(xié)議。
// 使用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] {
// 方法跟上個例子差不多, 就不實現(xiàn)了
return [:]
}
}
使用泛型約束能夠很容易的進行面向協(xié)議編程(POP)。