首页 Range-面向协议编程
文章
取消

Range-面向协议编程

image

本文主要讲解 Range 家族类的一些实现细节和 Swift 中面向协议编程的一些具体表现。为了方便起见,无论是 class 或者 struct 都统称为『类』。

基本介绍

在 Swift 4.0 之前 Range 家族一共有 4 种类型:

1
2
3
4
let rang: Range = 0.0..<1.0 // 半开区间
let closedRange: ClosedRange = 0.0...1.0 // 闭区间
let countableRange: CountableRange = 0..<1 // Countable 半开区间
let countableClosedRange: CountableClosedRange = 0...1 // Countable 闭区间

之后 Swift 4.0 上新增了 4 种类型:

1
2
3
4
let partialRangeThrough: PartialRangeThrough = ...1.0 // 单侧区间
let partialRangeFrom: PartialRangeFrom = 0.0... // 单侧区间
let partialRangeUpTo: PartialRangeUpTo = ..<1.0 // 单侧区间
let countablePartialRangeFrom: CountablePartialRangeFrom  = 1... // Countable 单侧区间

但到了 Swift 4.2 又只剩下 5 种类型,分别是:RangeClosedRangePartialRangeThroughPartialRangeFromPartialRangeUpTo,所有的 Countable 类型都是对应的 typealias

1
2
3
public typealias CountableRange<Bound> = Range<Bound>
public typealias CountableClosedRange<Bound> = ClosedRange<Bound>
public typealias CountablePartialRangeFrom<Bound> = PartialRangeFrom<Bound>

基本构成

Range 的所有类型都是一个拥有 Bound 泛型的 struct,并且这个 Bound 必须继承 Comparable 协议。

1
2
3
4
5
public struct Range<Bound> where Bound : Comparable
public struct ClosedRange<Bound> where Bound : Comparable
public struct PartialRangeThrough<Bound> where Bound : Comparable
public struct PartialRangeFrom<Bound> where Bound : Comparable
public struct PartialRangeUpTo<Bound> where Bound : Comparable

在 swift 标准库中绝大多数基础类型都实现了此协议,所以包括 StringDateIndexPath 等。

1
2
3
let stringRange = "a"..<"z"
let dateRange = Date()...Date()
let indexRange = IndexPath(item: 0, section: 0)...IndexPath(row: 1, section: 0)

当需要用一个自定义的类创建 Range 也只是需要继承 Comparable 协议,并实现相应方法即可,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Foo: Comparable {
    var value: Int
    static func < (lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value < rhs.value
    }
    
    init(_ v: Int) {
        value = v
    }
}

let range = Foo(1)...Foo(20)
foo.contains(Foo(2)) // true

而且 contains(:) 也被自动的实现了,这其实归功于 RangeExpression 协议:

1
public func contains(_ element: Self.Bound) -> Bool

究其原因是每个 Range 类型都有一个 extension:当泛型 Bound 遵守 Comparable 时扩展相应的类以现实 RangeExpression 协议。

1
extension Range : RangeExpression where Bound : Comparable 
1
extension ClosedRange : RangeExpression where Bound : Comparable 
1
extension PartialRangeThrough : RangeExpression where Bound : Comparable
1
extension PartialRangeFrom : RangeExpression where Bound : Comparable
1
extension PartialRangeUpTo : RangeExpression where Bound : Comparable

试想一下如果用面向对象的语言一般是如何实现 contains(:) 方法的?

Countable 的实现细节

前面讲到在 Swift 4.2 上所有的 Countable 类型都是 typealias,是否具有 Countable 能力被抽象到泛型 Bound 上,以 ClosedRange 为例

1
2
3
4
5
6
7
8
9
extension ClosedRange : Sequence where Bound : Strideable, Bound.Stride : SignedInteger {

    /// A type representing the sequence's elements.
    public typealias Element = Bound

    /// A type that provides the sequence's iteration interface and
    /// encapsulates its iteration state.
    public typealias Iterator = IndexingIterator<ClosedRange<Bound>>
}

可以看到为了继承 Sequence 协议,泛型 Bound 需要先继承 StrideableStrideable 协议定义如下:

1
2
3
4
5
6
7
8
public protocol Strideable : Comparable {
    /// A type that represents the distance between two values.
    associatedtype Stride : Comparable, SignedNumeric

    public func distance(to other: Self) -> Self.Stride

    public func advanced(by n: Self.Stride) -> Self
}

它有一个绑定类型 Stride 和两个需要实现的方法,那么Bound.Stride : SignedInteger 表示的就是Strideable 的绑定类型 Stride 需要继承 SignedInteger

总结下来 swift 通过泛型约束、协议绑定类型约束再结合 extension 能力,把 Countable 能力被抽象到泛型 Bound 上,最终由泛型 Bound 来决定 Range 是否具有 Sequence 能力。

为什么 Int 可以创建 Countable 的 Range

或许你只知道通过 Int 创建的 Range,它就是一个CountableRange,然而为什么是?首先 Int 继承于 FixedWidthInteger, SignedInteger

1
public struct Int : FixedWidthInteger, SignedInteger

SignedInteger 又继承于 BinaryInteger, SignedNumeric

1
2
public protocol SignedInteger : BinaryInteger, SignedNumeric {
}

BinaryInteger 在一定条件下又继承于 Strideable

1
public protocol BinaryInteger : CustomStringConvertible, Hashable, Numeric, Strideable where Self.Magnitude : BinaryInteger, Self.Magnitude == Self.Magnitude.Magnitude

继续查看BinaryIntegerStrideable 实现:

1
2
3
4
extension BinaryInteger {   
	public func distance(to other: Self) -> Int
    public func advanced(by n: Int) -> Self
}

会发现 Stride 类型就是 Int, 而 Int 本身就是继承于 SignedInteger,这样子就符合前面提到的 Bound.Stride : SignedInteger 条件。最后别忘了另外一个限定条件

1
where Self.Magnitude : BinaryInteger, Self.Magnitude == Self.Magnitude.Magnitude

Magnitude 是 Numeric 协议的绑定类型,Numeric定义如下:

1
2
3
4
public protocol Numeric : Equatable, ExpressibleByIntegerLiteral {
    associatedtype Magnitude : Comparable, Numeric
    public var magnitude: Self.Magnitude { get }
}

但未发现 BinaryInteger 有任何的 extension 给定 Magnitude 的类型。这只能说明 Magnitude 会在具体的类上被指定,回到 Int 上果然找到 Magnitude

1
2
3
public struct Int : FixedWidthInteger, SignedInteger {
    public typealias Magnitude = UInt
}

继续查看 UInt

1
2
3
public struct UInt : FixedWidthInteger, UnsignedInteger {
    public typealias Magnitude = UInt
}

UnsignedInteger 又继承于 BinaryInteger

1
2
public protocol UnsignedInteger : BinaryInteger {
}

所以Self.Magnitude : BinaryInteger, Self.Magnitude == Self.Magnitude.Magnitude 就相当于 Int.UInt : BinaryInteger, Int.UInt == Int.UInt.UInt。至此 Int 类型满足了一切条件,事实上不仅是 Int 整个 Int 家族和 UInt 家族类型都是符合这些条件,下面是关于IntUInt 粗略协议继承关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                                +---------------+   
                                |  Comparable   |    
                                +-------+-------+   
                                        ^
                                        |
                +-------------+   +-----+-------+
        +------>+   Numeric   |   | Strideable  |
        |       +------------++   +-----+-------+
        |                    ^          ^
        |                    |          |
+-------+-------+        +---+----------+----+ 
| SignedNumeric |        |   BinaryInteger   | 
+------+--------+        +---+-----+-----+---+
       ^         +-----------^     ^     ^----------+        
       |         |                 |                |  
+------+---------++    +-----------+--------+  +----+-------------+
|  SignedInteger  |    |  FixedWidthInteger |  |  UnsignedInteger |  
+---------------+-+    +-+----------------+-+  +--+---------------+
                ^        ^                ^       ^
                |        |                |       |
                |        |                |       |
               ++--------+-+             ++-------+--+     
               |Int family |             |UInt family|    
               +-----------+             +-----------+

手动实现 Strideable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Foo {
    var value: Int
    init(_ v: Int) {
        value = v
    }
}

extension Foo: Strideable {
    func distance(to other: Foo) -> Int {
        return other.value - self.value
    }
    
    func advanced(by n: Int) -> Foo {
        var result = self
        result.value += n
        return result
    }
}

Foo 继承 Strideable 的同时其绑定也被指定为 Int,这样子就可以创建自定义类型的 Range 了,并且继承于 Sequence 。

1
2
3
4
5
6
let fooRange = Foo(1)...Foo(20)
fooRange.contains(Foo(2))
Array((Foo(1)..<Foo(20)))
for item in fooRange {
    print(item)
}

总结

Swift 作为一门面向协议编程的语言,在 Range 的实现上可见一斑,随着 SE-0142SE-0143提案分别在 Swift 4.0 和 Swift 4.2 中被加入之后更是加强了在这方面的能力。

本文由作者按照 CC BY 4.0 进行授权

Operation VS DispatchWorkItem

关于图片的一些知识点