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)結(jié)構(gòu)體(struct) 以及 枚舉(enum) 增加新功能贫堰。在新版的 Swift 2.0 當中穆壕,我們也可以對 協(xié)議(protocol) 進行擴展了。

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

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

從這里開始(Getting Started)

我們先從新建一個 playground開始妙真。打開 Xcode 7 選擇 File\New\Playground... 并命名為 SwiftProtocols缴允。可以選擇任意的平臺珍德,因為本教程中的代碼是與平臺無關(guān)的练般。選擇保存的位置后點擊 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ū)底部增加如下的 結(jié)構(gòu)體 定義:

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

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

接著,再定義兩個結(jié)構(gòu)?體:

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 結(jié)構(gòu)體定義中的 let canFly = true 語句刪除∥醪簦可以看到未斑,playground 可以順利通過編譯,因為擴展協(xié)議的默認實現(xiàn)已經(jīng)幫你處理了這些瑣事币绩。

為何不使用基類蜡秽?

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

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

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

我們已經(jīng)體驗過了結(jié)構(gòu)體的實戰(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 通過在標準庫中增加了 mapreducefilter 等方法梅鹦,使它自身的函數(shù)式編程屬性得到了大大的提升裆甩。

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

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

對數(shù)組調(diào)用 map 方法返回了另一個數(shù)組齐唆,再對這個數(shù)組調(diào)用 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 類型調(diào)用 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é)議子房,但是由于結(jié)構(gòu)體不能被繼承,因此也就無法定義通用的實現(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 類型的集合調(diào)用 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é)議增加通用和默認的行為授药,這與使用基類相似,但是更靈活呜魄,因為它也適用于結(jié)構(gòu)體和枚舉烁焙。

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虑鼎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子键痛,更是在濱河造成了極大的恐慌炫彩,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件絮短,死亡現(xiàn)場離奇詭異江兢,居然都是意外死亡,警方通過查閱死者的電腦和手機丁频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門杉允,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人席里,你說我怎么就攤上這事叔磷。” “怎么了奖磁?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵改基,是天一觀的道長。 經(jīng)常有香客問我署穗,道長寥裂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任案疲,我火速辦了婚禮封恰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褐啡。我一直安慰自己诺舔,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布备畦。 她就那樣靜靜地躺著低飒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懂盐。 梳的紋絲不亂的頭發(fā)上褥赊,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音莉恼,去河邊找鬼拌喉。 笑死速那,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尿背。 我是一名探鬼主播端仰,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼田藐!你這毒婦竟也來了荔烧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汽久,失蹤者是張志新(化名)和其女友劉穎鹤竭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體回窘,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡诺擅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啡直。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烁涌。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酒觅,靈堂內(nèi)的尸體忽然破棺而出撮执,到底是詐尸還是另有隱情,我是刑警寧澤舷丹,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布抒钱,位于F島的核電站,受9級特大地震影響颜凯,放射性物質(zhì)發(fā)生泄漏谋币。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一症概、第九天 我趴在偏房一處隱蔽的房頂上張望蕾额。 院中可真熱鬧,春花似錦彼城、人聲如沸诅蝶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽调炬。三九已至,卻和暖如春舱馅,著一層夾襖步出監(jiān)牢的瞬間缰泡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工代嗤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匀谣,地道東北人照棋。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像武翎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子溶锭,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容