Swift 中的 Range

image

本文主要講解 Range 家族類的一些實現細節和 Swift 中面向協議編程的一些具體表現。爲了方便起見,無論是 class 或者 struct 都統稱爲『類』。

基本介紹

在 Swift 4.0 之前 Range 家族一共有 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 種類型:

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

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

基本構成

Range 的所有類型都是一個擁有 Bound 泛型的 struct,並且這個 Bound 必須繼承 Comparable 協議。

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 等。

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

當需要用一個自定義的類創建 Range 也只是需要繼承 Comparable 協議,並實現相應方法即可,例如

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 協議:

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

究其原因是每個 Range 類型都有一個 extension:當泛型 Bound 遵守 Comparable 時擴展相應的類以現實 RangeExpression 協議。

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

試想一下如果用面向對象的語言一般是如何實現 contains(:) 方法的?

Countable 的實現細節

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

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 協議定義如下:

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

public struct Int : FixedWidthInteger, SignedInteger

SignedInteger 又繼承於 BinaryInteger, SignedNumeric

public protocol SignedInteger : BinaryInteger, SignedNumeric {
}

BinaryInteger 在一定條件下又繼承於 Strideable

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

繼續查看BinaryIntegerStrideable 實現:

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

會發現 Stride 類型就是 Int, 而 Int 本身就是繼承於 SignedInteger,這樣子就符合前面提到的 Bound.Stride : SignedInteger 條件。最後別忘了另外一個限定條件

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

Magnitude 是 Numeric 協議的綁定類型,Numeric定義如下:

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

但未發現 BinaryInteger 有任何的 extension 給定 Magnitude 的類型。這隻能說明 Magnitude 會在具體的類上被指定,回到 Int 上果然找到 Magnitude

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

繼續查看 UInt

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

UnsignedInteger 又繼承於 BinaryInteger

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 粗略協議繼承關係。

                                +---------------+   
                                |  Comparable   |    
                                +-------+-------+   
                                        ^
                                        |
                +-------------+   +-----+-------+
        +------>+   Numeric   |   | Strideable  |
        |       +------------++   +-----+-------+
        |                    ^          ^
        |                    |          |
+-------+-------+        +---+----------+----+ 
| SignedNumeric |        |   BinaryInteger   | 
+------+--------+        +---+-----+-----+---+
       ^         +-----------^     ^     ^----------+        
       |         |                 |                |  
+------+---------++    +-----------+--------+  +----+-------------+
|  SignedInteger  |    |  FixedWidthInteger |  |  UnsignedInteger |  
+---------------+-+    +-+----------------+-+  +--+---------------+
                ^        ^                ^       ^
                |        |                |       |
                |        |                |       |
               ++--------+-+             ++-------+--+     
               |Int family |             |UInt family|    
               +-----------+             +-----------+

手動實現 Strideable

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 。

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 中被加入之後更是加強了在這方面的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章