從Swift3的標準庫協議看面向協議程式設計(一)

一銘發表於2017-12-13

本文主要是從 Hashable作為入口,瞭解Swift3的標準庫協議.再研究從自定義的類去遵循 Sequence和 Collection 兩大基礎協議,從而加深Swift的面向協議程式設計的思想

入坑 Swift的時候看了一段演講,這是連結.這讓我對Swift的面向協議程式設計有了興趣.

Swift中,大量內建類如Dictionary,Array,Range,String都使用了協議,在學習的過程中我把自己的學習筆記記錄下來.

2016-09-14: 更新完 Xcode8以後,根據swift2.2寫好的,又要重寫一遍了.....


##先看看Hashable 雜湊表是一種基礎的資料結構.,Swift中字典具有以下特點:字典由兩種範型型別組成,其中 key 必須實現 Hashable 協議.關於 swift 中字典是怎麼實現的,可以看這篇 .

public protocol Hashable : Equatable {
    public var hashValue: Int { get }
}
複製程式碼

可以看到 Hashable遵循了 Equable,那再來看看 Equable

public protocol Equatable {
    public func ==(lhs: Self, rhs: Self) -> Bool
}
複製程式碼

看來遵循 Equable 都必須過載這個 == ,來定義自己的判等方法. 上 sample:

struct MyPoint: Hashable, Comparable {
    var x: Int
    var y: Int
    var hashValue: Int {
        get {
            return x.hashValue + y.hashValue
        }
    }
}
func ==(lhs: MyPoint, rhs: MyPoint) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

let pointA = MyPoint(x: 1, y: 1)
let pointB = MyPoint(x: 1, y: 1)
let pointC = MyPoint(x: 1, y: 2)
pointA == pointB //true
pointA == pointC //false
複製程式碼

如果需要比較大小需要遵循 Comparable這個協議,協議需要內容就不貼了,直接上 code:

func <(lhs: MyPoint, rhs: MyPoint) -> Bool {
    return lhs.hashValue < rhs.hashValue
}
func <=(lhs: MyPoint, rhs: MyPoint) -> Bool {
    return lhs.hashValue <= rhs.hashValue
}
func >(lhs: MyPoint, rhs: MyPoint) -> Bool {
    return lhs.hashValue > rhs.hashValue
}
func >=(lhs: MyPoint, rhs: MyPoint) -> Bool {
    return lhs.hashValue >= rhs.hashValue
}

pointA >= pointB //true
pointA > pointC //false
pointA < pointC //true
複製程式碼

借用 Mattt的話來做下總結:

在 Swift 中,Equatable 是一個基本型別,由此也演變出了 Comparable 和 Hashable 兩種型別。這三個一起組成了這門語言關於物件比較的核心元素。

##再看看Sequence (這部分 Swift3有變化) ####SequenceType(Swift 2.x) -> Sequence (Swift 3.0) SequenceType在喵神的 Swifttips 裡面已經講解的很好了,我還是把自己的例子寫了下來.這部分程式碼是 Swift3版本下的.

public protocol SequenceType {
   associatedtype Iterator : IteratorProtocol
    //在3.0以前是GeneratorType
    ........
}
複製程式碼

IteratorProtocol又是什麼呢?其實GeneratorType一樣,可以理解為生成器

public protocol IteratorProtocol {
    associatedtype Element
    public mutating func next() -> Self.Element?
}
複製程式碼

associatedtype Element要求實現這個協議的類必須定義一個名為Element的別名,這樣一定程度上實現了泛型協議。協議同時要求實現next函式,其返回值是別名中定義的Element型別,next函式代表生成器要生成的下一個元素。 sample 是來自連結,寫的非常清楚,我這只是貼貼我的 playground

struct Book {
    var name: String = ""
    var price: Float = 0.0
    init(name: String, price: Float) {
        self.name = name
        self.price = price
    }
}//定義一個 Book 的 Struct, 有書名和價格

class BookListIterator: IteratorProtocol {
    typealias Element = Book //將 Book 類賦值 Element

    var currentIndex: Int = 0
    var bookList: [Book]?

    init(bookList: [Book]) {
        self.bookList = bookList
    } //初始化方法

//用來遍歷 bookList,直到返回 nil
    func next() -> BookListIterator.Element? {
        guard let list = bookList else { return nil }

        if currentIndex < list.count {
            let element = list[currentIndex]
            currentIndex += 1
            return element
        } else {
            return nil
        }
    }
}
複製程式碼

現在IteratorProtocol這個生成器已經寫好了,可以寫 Sequence了

class BookList: Sequence {

    var bookList: [Book]?

    init() {
        self.bookList = [Book]()
    }

    func addBook(book: Book) {
        self.bookList?.append(book)
    }

// associatedtype Iterator : IteratorProtocol
    typealias Iterator = BookListIterator
    
//public func makeIterator() -> Self.Iterator
   func makeIterator() -> BookList.Iterator {
        return BookListIterator(bookList: self.bookList!)
    }
}
複製程式碼

來試試寫的 BookList:

let bookList = BookList()

bookList.addBook(book: Book(name: "Swift", price: 12.5))
bookList.addBook(book: Book(name: "iOS" , price: 10.5))
bookList.addBook(book: Book(name: "Objc", price: 20.0))

for book in bookList {
    print("\(book.name) 價格 ¥\(book.price)")
}
// Swift 價格 ¥12.5 
// iOS 價格 ¥10.5
// Objc 價格 ¥20.0
複製程式碼

而且不止可以使用 for...in, 還可以用 map , filter 和 reduce. 再談談 Swift3的變化,其實就是變了GeneratorType To IteratorProtocol,就是這麼任性....

##從 Sequence 到 Collection ####SequenceType(Swift 2.x) -> Sequence (Swift 3.0) 如果現在我們要看 bookList的 count, 就牽扯到了Collection這個協議,可以發現這個協議是對Indexable 和 Sequence 的擴充套件. ####重點看看這個Indexable 在2.x的時候,Indexable 並沒有繼承任何其他協議,那麼3.0來了,來了個IndexableBase:

public protocol Indexable : IndexableBase
複製程式碼

那再來看IndexableBase:

//2.x版本indexable
var endIndex: Self.Index 
var startIndex: Self.Index 
subscript(_: Self.Index) 

//新增的下標以及例項方法
subscript(_: Range<Self.Index>) 
func formIndex(after:) 
func index(after:) 
複製程式碼

再回到 Collection, 如果我們的型別已經遵循了Sequence,那麼就只需要遵循:

var startIndex: Int
var endIndex: Int
subscript(_: Self.Index) 
func index(after:) 
複製程式碼

這四個需求中,startIndex和endIndex是為了 Collection 中要遵循 Indexable協議,還得實現一個下標索引來獲取對應索引元素.在 Swift3中,還需要宣告 index(after:),關於這個戳swift-evolutionl連結. 再看看怎麼對 Sample 例子中BookList遵循 Collection

extension BookList: Collection {

    typealias Element = Book

    var startIndex: Int {
        return 0
    }

    var endIndex: Int {
        return bookList!.count
    }

    subscript(i: Int) -> Element {
        precondition((0..<endIndex).contains(i), "index out of bounds")
        return bookList![i]
    }

    func index(after i: Int) -> Int {
        if i < endIndex {
            return i + 1
        } else {
            return endIndex
        }
    }
}
複製程式碼

是幾個屬性方法的實現還是挺簡單的,現在BookList 這個 class,既遵循了 Sequence 和 Collection, 就有超過40種方法和屬性可以使用:

booklist.first //(Book(name: "Swift", price: 12.5))
booklist.count // 3
booklist.endIndex // 3
booklist.isEmpty //false
複製程式碼

現在自己建立的型別就已經遵循了 Sequence和 Collection,還有map,reduce 等函式式方法可以使用.

相關文章