Swift 2.0之初識面向協(xié)議編程

本文翻譯自:Ray Wenderlich
原文地址:Introducing Protocol-Oriented Programming in Swift 2

Swift Bird brings speedy new features to Swift 2!
Swift Bird brings speedy new features to Swift 2!

說明:本教程使用了最新的 beta 版 Xcode 7 與 Swift 2.0,它們的正式版將會在今年的秋季發(fā)布。目前可以到蘋果的開發(fā)者官網(wǎng)上下載最新的 beta 版 Xcode。

在 WWDC 2015上,蘋果發(fā)布了 Swift 2.0 版本,這是自 Swift 發(fā)布以來進行的第二次重大改版,這次改進推出了很多新特性來幫助我等程序員寫出更優(yōu)雅的代碼。

在這些新特性里面,最讓人興奮的莫過于 協(xié)議擴展(protocol extensions) 了。在第一版的 Swift 當中,我們可以使用擴展來為 類(class)、結構體(struct) 以及 枚舉(enum) 增加新功能。在新版的 Swift 2.0 當中,我們也可以對 協(xié)議(protocol) 進行擴展了。

這乍看起來好像只是一個很小的改變,事實上,協(xié)議擴展的功能是相當強大的,它甚至能改變我們編寫代碼的方式。在本教程中,你不僅可以學習到創(chuàng)建和使用協(xié)議擴展的方法,同時還可以體會到這項新技術和面向協(xié)議編程范式給你帶來的新視野。

同時我們還能看到 Swift 開發(fā)小組是如何使用協(xié)議擴展來對 Swift 的標準庫進行改進,以及它將會對我們編寫代碼帶來怎樣的影響。

從這里開始(Getting Started)

我們先從新建一個 playground開始。打開 Xcode 7 選擇 File\New\Playground... 并命名為 SwiftProtocols??梢赃x擇任意的平臺,因為本教程中的代碼是與平臺無關的。選擇保存的位置后點擊 Next,最后點擊 Create。

打開 playground 后,添加如下代碼:

protocol Bird {
    var name: String { get }
    var canFly: Bool { get }
}

protocol Flyable {
    var airspeedVelocity: Double { get }
}

這里定義了一個簡單的 Bird 協(xié)議,它擁有 namecanFly 兩個屬性,同時還定義了一個擁有 airspeedVelocity 屬性的 Flyable 協(xié)議。

在沒有協(xié)議的遠古時期,我們可能會將 Flyable 定義為一個基類,然后使用繼承的方式來定義 Bird 以及其它會飛的東西,比如飛機之類的。然而這里并不用這么做,這里的所有一切都是從協(xié)議開始的。

當我們接下來開始定義具體類型的時候,你將會看到這種方法將會使我們的整個系統(tǒng)更加靈活。

定義遵守協(xié)議的類型

在代碼區(qū)底部增加如下的 結構體 定義:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true
  var airspeedVelocity: Double {
    return 3 * flappyFrequency * flappyAmplitude
  } 
}

這里定義了一個新的結構體 Flappybird,這個結構體遵守了 BirdFlyable 協(xié)議。它的 airspeedVelocity 屬性的值是通過 flappyFrequencyflappyAmplitue 計算出來的。作為一只像素鳥,它的 canFly 當然是返回 true的 :]。

接著,再定義兩個結構?體:

struct Penguin: Bird {
    let name: String
    let canFly = false
}

struct SwiftBird: Bird, Flyable {
   var name: String { return "Swift \(version)" }
   let version: Double
   let canFly = true

   // Swift 速度超群!
   var airspeedVelocity: Double { return 2000.0 }
}

企鵝(Penguin)是一種鳥(Bird),不過它是不能飛的。啊哈,這時應該慶幸我們沒有采用繼承的方法,使用繼承會讓所有的子類都擁有飛的能力。雨燕鳥(SwiftBird)不僅能飛,它還擁有超快的速度!

我們可以發(fā)現(xiàn),上面的代碼里面有一些冗余。盡管我們已經(jīng)有了 Flyable 的信息,但是我們還是得為每個 Bird 類型指定 canFly 屬性來表明它是否可以飛行。

擁有默認實現(xiàn)的擴展協(xié)議

對于協(xié)議擴展,我們可以為它指定默認的實現(xiàn)。在定義 Bird 協(xié)議的下方增加如下代碼:

extension Bird where Self: Flyable {
    // Flyable birds can fly!
    var canFly: Bool { return true }
}

這里通過對 Bird 協(xié)議進行擴展,為它增加了默認行為。當一個類同時遵守 BirdFlyable 協(xié)議時,它的 canFly 屬性就默認返回 true。即是說,所有遵守 Flyable 協(xié)議的鳥類都不必再顯式指定它是否可以飛行了。

Swift 1.2 將 where 判斷語法增加到了 if-let 綁定中,而 Swift 2.0 更進一步地將這個語法帶到了協(xié)議擴展中。

FlappyBirdSwiftBird 結構體定義中的 let canFly = true 語句刪除??梢钥吹剑?code>playground 可以順利通過編譯,因為擴展協(xié)議的默認實現(xiàn)已經(jīng)幫你處理了這些瑣事。

為何不使用基類?

或許你會發(fā)現(xiàn),使用協(xié)議擴展及其它的默認實現(xiàn)與使用基類,或者其它語言中的抽象類很相似,但是它有幾個關鍵的優(yōu)勢:

  • 因為一個類型可以遵守多個協(xié)議,所以它可以從各個協(xié)議中接收到不同的默認實現(xiàn)。與其它語言中所支持的多重繼承不同(吐槽:說的就是C++吧),協(xié)議擴展不會為遵守它的類型增加額外的狀態(tài)。
  • 所有的類,結構體和枚舉都可以遵守協(xié)議,而基類只能被類所繼承。

換句說就是,協(xié)議擁有為值類型(value types)增加默認實現(xiàn)的能力,而不僅僅是類。

我們已經(jīng)體驗過了結構體的實戰(zhàn),接下來在 playground 的底部增加如下的枚舉的定義:

enum UnladenSwallow: Bird, Flyable {
  case African
  case European
  case Unknown
  var name: String {
    switch self {
      case .African:
        return "African"
      case .European:
        return "European"
      case .Unknown:
        return "What do you mean? African or European?"
    }
  }
 
  var airspeedVelocity: Double {
    switch self {
      case .African:
        return 10.0
      case .European:
        return 9.9
      case .Unknown:
        fatalError("You are thrown from the bridge of death!")
    } 
  }
}

與其它值類型一樣,你所需要做的就是定義一些屬性,好讓 UnladenSwallow 能夠遵守這兩個協(xié)議。因為這個枚舉同時遵守了 BirdFlyable 所以它也得到了 canFly 的默認實現(xiàn)。

看到這里,你是不是以為這篇教程只是為了演示一些小鳥的飛行把戲?
接下來,讓我們看一些更有實戰(zhàn)意義的代碼。

擴展協(xié)議(Extending Protocols)

協(xié)議擴展最常用的約莫就是擴展外部協(xié)議了,不管這些協(xié)議來自 Swift 標準庫還是來自第三方框架。

playground 的底部再增加如下代碼:

extension CollectionType {
   func skip(skip: Int) -> [Generator.Element] {
     guard skip != 0 else { return [] }
     
     var index = self.startIndex
     var result: [Generator.Element] = []
     var i = 0
     repeat {
       if i % skip == 0 {
         result.append(self[index])
       }
       index = index.successor()
       i++
     } while (index != self.endIndex)

     return result
   }
}

這里對標準庫中的 CollectionType 進行了擴展,定義了一個方法。這個方法會對一個集合類型中的元素以 skip 步進行跳躍,然后返回集合中沒有被跳過的元素。

在 Swift 中,CollectionType 協(xié)議被類似數(shù)組以及字典這樣的集合類型所遵守。這意味著,現(xiàn)在你的整個 app 中,所有遵守 CollectionType 的類型都擁有這個方法了。接著在底部增加下面的代碼:

let bunchaBirds: [Bird] =
  [UnladenSwallow.African,
   UnladenSwallow.European,
   UnladenSwallow.Unknown,
   Penguin(name: "King Penguin"),
   SwiftBird(version: 2.0),
   FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)]

bunchaBirds.skip(3)

這里我們定義了一個數(shù)組,這個數(shù)組包含了大部分之前定義過的鳥類。因為數(shù)組類型遵守了 CollectionType 協(xié)議,所以,我們可以直接對這個數(shù)組使用 skip 方法。

擴展自己的協(xié)議(Extending Your Own Protocols)

跟為標準庫增加方法同樣令人興奮的是,我們也可以為它增加默認行為。

修改鳥類的協(xié)議,使其遵守 BooleanType 協(xié)議:

protocol Bird: BooleanType {

遵守 BooleanType 協(xié)議意味著所有 Bird 類型都要有一個 boolValue 屬性,使得它能夠像布爾值一樣被使用。這是不是說我們得為所有的定義過的,還有將來要定義的 Bird 類型添加這個屬性?

當然不是,使用協(xié)議擴展能讓我們有更簡便的辦法。在 Bird 的定義下面添加如下代碼:

extension BooleanType where Self: Bird {
  var boolValue: Bool {
    return self.canFly
  }
}

這個擴展可以讓 canFly 屬性來表示每個 Bird 類型的布爾值。

通過下面的代碼來試驗一下:

if UnladenSwallow.African {
  print("I can fly!")
} else {
  print("Guess I’ll just sit here :[")
}

可以看到控制臺打印出了 "I can fly!"。然而更值得注意的是,我們這里直接把 UnladenSwallow.African 丟到了 if 判斷里面!

對 Swift 標準庫的影響

我們已經(jīng)看到了,使用協(xié)議擴展可以極大地增強了我們代碼的功能和可定制性。然而你可能不知道的是,Swift 開發(fā)小組甚至使用了協(xié)議擴展對 Swift 標準庫的寫法進行了改進。

Swift 通過在標準庫中增加了 map、reducefilter 等方法,使它自身的函數(shù)式編程屬性得到了大大的提升。

這些方法存在于不同的 CollectionType 成員中,比如 Array

// Counts the number of characters in the array
["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // returns 9

對數(shù)組調用 map 方法返回了另一個數(shù)組,再對這個數(shù)組調用 reduce 方法,使其計算出整個數(shù)組中的字符數(shù)。

在這里,mapreduce 是包含在 Array當中的。如果我們按住 command 鍵點擊 map,就可以看到它的定義。

在 Swift 1.2 里,我們可以看到類似下面的定義:

// Swift 1.2
extension Array : _ArrayType {
    /// Return an `Array` containing the results of calling 
    /// `transform(x)` on each element `x` of `self`
    func map<U>(transform: (T) -> U) -> [U]
}

在這里 map 函數(shù)是作為 Array 的擴展被定義的。但是 Swift 的函數(shù)式函數(shù)不止是對 Array ,而是對所有的 CollectionType 都起作用,那么 Swift 1.2 是如何處理的呢?

如果對一個 Range 類型調用 map 函數(shù),并且從那里跳到它的實現(xiàn),我們可以看到如下的定義:

// Swift 1.2
extension Range {
    /// Return an array containing the results of calling 
    /// `transform(x)` on each element `x` of `self`. 
    func map<U>(transform: (T) -> U) -> [U]
}

可以發(fā)現(xiàn),對于 Swift 1.2 來說,標準庫中不同的 CollectionType 都需要重新實現(xiàn) map 函數(shù)。
這是因為雖然 ArrayRange 都遵守了 CollectionType 協(xié)議,但是由于結構體不能被繼承,因此也就無法定義通用的實現(xiàn)。

這不僅僅是標準庫實現(xiàn)上的一點細微差別,這實際上限制對 Swift 類型的使用。

下面這個范型函數(shù)接受一個 Flyable 類型的 CollectionType,然后返回擁有最快速度(airspeedVelocity)的元素:

func topSpeed<T: CollectionType where T.GeneratorType: Flyable>(collection: T) -> Double {
  collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}

在 Swift 1.2 當中沒有協(xié)議擴展,因此這段代碼會報錯。mapreduce 函數(shù)只存在預定義的一些類型中,并不能對任意的 CollectionType 起作用。

然而在 Swift 2.0 中使用了協(xié)議擴展,對于 ArrayRangemap 函數(shù)都是這樣定義的:

// Swift 2.0
extension CollectionType {
  /// Return an `Array` containing the results of mapping `transform`
  /// over `self`.
  ///
  /// - Complexity: O(N).
  func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

雖然無法看到 map 方法的實現(xiàn) -- 至少在 Swift 2.0 開源之前,但是我們可以知道所有的 CollectionType 都有一個 map 方法的默認實現(xiàn)。即是說,所有遵守 CollectionType 的類型,都能隨機附贈一個 map 方法。

playground 的最底部增加如下的泛型函數(shù):

func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(c: T) -> Double {
  return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}

可以對保存 Flyable 類型的集合調用 mapreduce 方法了。如果,你還對上面定義的鳥類中哪個速度最快持有疑惑的話,就可以使用這個函數(shù)來得到最終的答案了:

let flyingBirds: [Flyable] = 
  [UnladenSwallow.African,
  UnladenSwallow.European,
  SwiftBird(version: 2.0)]
 
topSpeed(flyingBirds) // 2000.0

接下來做什么(Where To Go From Here)

你可以在這里下載到完整的 `playground。

通過定義自己的簡單協(xié)議,并對它們使用協(xié)議擴展,我們已經(jīng)見識到了面向協(xié)議編程的強大了。通過默認實現(xiàn),我們可以給已經(jīng)存在的協(xié)議增加通用和默認的行為,這與使用基類相似,但是更靈活,因為它也適用于結構體和枚舉。

更進一步,協(xié)議擴展不僅可以用來擴展自定義的協(xié)議,還可以對 Swift 標準庫,Cocoa 和 CocoaTouch 的協(xié)議進行擴展,并提供默認行為。

如果想知道 Swift 2 還更新了哪些其它新特性,可以參考我們的另一篇文章Swift 2 新特性,或者 Swift 2 公布的官方博客。

同時還可以觀看 WWDC 的Protocol Oriented Programming來進行更加深入的學習,以及獲得更底層的理論知識。

最后再打個小廣告:),本人博客地址:Swiftyper

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容