Swift4 中的泛型约束

源链接: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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值