源链接:http://www.cocoachina.com/ios/20171025/20903.html
范型可以说是 Swift 跟 OC 相比最大的优势了。通过给像集合这类东西关联泛型, 可以写出更可预测并且更安全的代码。在 Swift4 中类型约束更为强大, 它能够让我们更能够轻而易举的做很多事情。即使是通用代码, 也能充分的利用 Swift 的类型系统。
例1:
首先我们来看看一个简单的例子。比如说给一个数字数字求和。我们可能会些这样的代码:
1
2
3
4
5
6
|
// 在这段代码中, 我们定义了一个方法, 接受一个 Int 数组作为参数, 在方法内部使用高阶函数 reduce 最后返回这个结果。
func sum(_ numbers: [Int]) -> Int {
return
numbers.reduce(
0
, +)
}
let array = [
1
,
2
,
3
,
4
,
5
]
sum(array)
// 15
|
使用泛型约束, 我们可以这样来实现这个需求:
1
2
3
4
5
6
7
|
// 在这段代码中, 我们给 Array 添加了类型约束的 Extension。当数组的 Element 遵守了 Numeric 协议的时候, Array 就拥有 sum 这个方法。
extension
Array
where Element: Numeric {
func sum() -> Element {
return
reduce(
0
, +)
}
}
array.sum()
// 15
|
两者相比, 使用泛型约束最大的优势是使用扩展, 能够让这个功能跟调用者更紧密。比较一下:
1
2
|
let sum = sum(array)
let sum = array.sum()
|
在 OC 里面, 可能有些同学会写一个 XXXTool 之类的类, 来封装这种类型的功能。 或者是直接写成 C 的函数。但是不论怎么写, 这样貌似都不是特别的 OOP。或者OC 还可以直接给 NSArray 加一个 category, 然后再实现相似的功能。但是, 这样做不就等于所有的 array 都具有这个功能了吗?
例2
再来看这样的需求: 计算某个包含字符串集合中有多少个单词。我们可以通过给集合添加一个扩展轻松的完成这件事情。给 Collection 添加一个约束, 限制集合中的 Element 是 String类型:
1
2
3
4
5
6
7
8
9
10
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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。这时候肯定会想要定义一个协议:
1
2
3
|
protocol ModelManager {
associatedtype Model
}
|
现在我们再来定一个查找符合某个条件的方法:传入某个查询条件, 然后返回符合这个条件的模型数组。
1
2
3
4
|
protocol ModelManager {
associatedtype Model
func models(matching query:
String
) -> [Model]
}
|
这个时候, 这个协议变成了这样。这个样子依然有问题,不够灵活, 而且还有一个很恐怖的问题: 硬编码。接下来我们试着使用范型约束来使用 Swift 的类型系统, 让这个功能更灵活, 并且使用类型系统来解决硬编码的问题。
接下来, 再给 ModelManager 关联两个类型, Query 和 Collection。Query 用来描述查询的条件。他可以是任何东西, 只要能够描述查询条件就可以。当然, 个人认为可能 enum 是最好的选择。Collection用来描述查询结果, 他用来限制返回的结果就是这个管理类的模型。现在这个协议就成这样了:
1
2
3
4
5
6
|
protocol ModelManager {
associatedtype Model
associatedtype Collection: Swift.Collection where Collection.Element == Model
associatedtype Query
func models(matching query: Query) -> Collection
}
|
有了上面的基础, 就可以很方便的实现一些具有查询功能的模型管理类了。比如说我们要用户管理类, 需要通过用户姓名和年龄段来查询符合要求的用户:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// 定义 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] {
// 这里做了几个假的数据
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}]</int>
|
对有些模型来说, 使用字典来作为返回的 Collection可能是更好的方法。下面这个例子是用来通过影片名称和导演名字来筛选电影的例子。返回的结果通过电影分类来做分类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
// 定义电影分类的枚举, 因为要作为字典的 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)。