面向協(xié)議編程介紹(一切始于協(xié)議)

本篇文章翻譯自:Introducing Protocol-Oriented Programming in Swift 2)
原作:[Erik kerber]
(https://www.raywenderlich.com/u/kerber) on June 25, 2015


注: 這篇tutorial需要XCode 7, Swift2, 這個(gè)秋天會(huì)發(fā)布測(cè)試版病蛉。你也可以在 Apple's developer portal下載最新版本。
WWDC 2015, 蘋果發(fā)布了swift 2,宣布了swift語(yǔ)言第二次重大修訂铺然,其中包括了幾個(gè)新的語(yǔ)言特征俗孝,以幫助你提升編碼方式。
在這些的新特征中魄健,最令人興奮的是協(xié)議擴(kuò)展赋铝。在swift第一個(gè)版本,你可以擴(kuò)展已經(jīng)存在的類沽瘦,結(jié)構(gòu)體革骨,和枚舉類型的功能。現(xiàn)在有了swift 2析恋,你也可以擴(kuò)展協(xié)議良哲。
可能剛開始你會(huì)覺得這是一個(gè)微不足道的特征,但是協(xié)議擴(kuò)展真的能量巨大助隧,且可以轉(zhuǎn)變你的編碼方式筑凫。在這篇turorial,你會(huì)探索創(chuàng)建,使用協(xié)議擴(kuò)展的方法并村,還有新技術(shù)和面向協(xié)議編程模式(以下簡(jiǎn)稱:POP)巍实。
你將會(huì)看到Swift團(tuán)隊(duì)是怎樣使用協(xié)議擴(kuò)展以提升swift標(biāo)準(zhǔn)庫(kù),和協(xié)議擴(kuò)展怎樣影響你的代碼哩牍。

開始

開始在XCode中創(chuàng)建一個(gè)新的Playground, 選擇File\New\Playground...蔫浆,然后命名Playground為SwiftProtocols。你可以選擇任何平臺(tái)姐叁,因?yàn)檫@篇tutorial所有的代碼跟平臺(tái)無關(guān)瓦盛。點(diǎn)擊Next選擇你要保存的路徑,然后點(diǎn)擊Create.
Playground打開后外潜,我們添加如下代碼:

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

     protocol Flyable {
         var airspeedVelocity: Double { get }
     }
}

這里簡(jiǎn)單的定義了一個(gè)擁有2個(gè)屬性name, 和canFly的Bird協(xié)議類型原环,還有一個(gè)Flyable類型,它定義了airspeedVelocity屬性处窥。
在pre-protocol時(shí)期嘱吗,你可能會(huì)把Flyable當(dāng)做基類,然后依賴?yán)^承去定義Bird還有其他能fly的東西滔驾,例如:灰機(jī)谒麦。請(qǐng)記住:一切始于協(xié)議哆致!

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

在Playground下方添加struct的定義:

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

這里定義了一個(gè)新的結(jié)構(gòu)體FlappyBird, 它遵守Bird和Flyable協(xié)議绕德,airspeedVelocity為計(jì)算屬性。canFly屬性設(shè)置為true摊阀。
接下來耻蛇,在playground下面添加以下2個(gè)結(jié)構(gòu)體的定義:

struct Penguin: Bird {     
   let name: String     
   let canFly: Bool
}

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

企鵝是鳥類踪蹬,但不會(huì)飛。哈--- 還好你沒有使用繼承臣咖,讓鳥類都能飛跃捣。海燕當(dāng)然有非常快的飛行速度夺蛇。
你可能已經(jīng)看到一些冗余疚漆。盡管在你的結(jié)構(gòu)中已經(jīng)有了Flayable的概念,每一種Bird類型還必須聲明是否canFly刁赦。

用默認(rèn)實(shí)現(xiàn)擴(kuò)展協(xié)議

使用協(xié)議擴(kuò)展娶聘,你可以為協(xié)議定義默認(rèn)行為。在Bird協(xié)議下添加如下代碼:

extension Bird where Self: Flyable {     
   var canFly: Bool { return true }
}

這里定義Bird的一個(gè)擴(kuò)展截型,當(dāng)類型也為Flyable時(shí),設(shè)置為canFly設(shè)置默認(rèn)行為true儒溉。換句話說宦焦,任何一個(gè)遵守Flyable的Bird類型不必在顯示聲明。

i wanna everything automatic

swift 1.2 引入了where語(yǔ)法顿涣,之后swift 2使用where語(yǔ)法給協(xié)議擴(kuò)展添加約束(Self作用是指波闹,當(dāng)Bird類型同時(shí)也遵守Flyable協(xié)議,canFly屬性才能生效)
現(xiàn)在從FlappyBird和SwiftBird結(jié)構(gòu)體聲明中刪除let canFly = true涛碑。你會(huì)發(fā)現(xiàn)playground成功編譯了精堕,因?yàn)閰f(xié)議擴(kuò)展幫你處理了協(xié)議的要求。

為什么不用基類

協(xié)議擴(kuò)展和其默認(rèn)實(shí)現(xiàn)好像跟其他語(yǔ)言的基類或者抽象類很相似蒲障,但是swift具有一下幾個(gè)關(guān)鍵優(yōu)勢(shì):

  • 因?yàn)轭愋涂梢宰袷囟鄠€(gè)協(xié)議歹篓,可以擁有多個(gè)協(xié)議默認(rèn)實(shí)現(xiàn)的功能。不像其他編程語(yǔ)言支持的類的多繼承揉阎,協(xié)議擴(kuò)展不會(huì)引入額外的狀態(tài)庄撮。
  • 協(xié)議可以被類,結(jié)構(gòu)體和枚舉類型遵守毙籽《此梗基類和繼承受限于類類型(class類型)
    換句話說,協(xié)議擴(kuò)展為值類型坑赡,而不僅僅是為類類型提供了定義默認(rèn)行為的能力烙如。
    你已經(jīng)在結(jié)構(gòu)體中見識(shí)過這種行為;接下來在playground添加枚舉定義:
enum UnladenSwallow: Bird, Flyable {
    case Africancase
    case Europeancase
    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!")}
    }
}

像其他值類型一樣毅否,遵守Bird和Flyable協(xié)議亚铁,你只需要定義協(xié)議要求的屬性。因?yàn)樗袷剡@2個(gè)協(xié)議螟加,所以UnladenSwallow的canFly屬性也獲得了默認(rèn)實(shí)現(xiàn)刀闷。
你真的認(rèn)為該 tutorial只會(huì)包括airspeedVelocity屬性熊泵,而不會(huì)包括Monty Python的引用。(哈甸昏,可擴(kuò)展顽分。。施蜜。)

擴(kuò)展協(xié)議

可能協(xié)議擴(kuò)展最普遍的用法就是擴(kuò)展外部協(xié)議了卒蘸,可能是定義在swift標(biāo)準(zhǔn)庫(kù)的協(xié)議,也可能是定義在第三方框架的協(xié)議翻默。在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 += 1
        }while ( index != self.endIndex)
        return result
    }
}

在CollectionType類型上擴(kuò)展了一個(gè)新的方法skip(:), 它會(huì)遍歷集合中的每一個(gè)元素缸沃,過濾掉不符合要求的元素(留下索引值能被參數(shù)skip整除對(duì)應(yīng)的元素),然后返回一個(gè)過濾后的數(shù)組修械。
CollectionType是一個(gè)被諸如swift中數(shù)組趾牧,字典等類型遵守的協(xié)議。這就意味著這個(gè)新的行為(skip(
:) 方法)存在你APP中所有的CollectionType類型中肯污。playground中添加如下代碼,我們來見證這一行為:

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

這里哄芜,你定義了一個(gè)包含各種類型鳥類的數(shù)組柬唯,這些類型上文中你已經(jīng)定義過认臊。因?yàn)閿?shù)組遵守CollectionType, 那么它就能用skip(_:)方法。

擴(kuò)展你自己的協(xié)議

也許讓你興奮的是你可以像給swift標(biāo)準(zhǔn)庫(kù)添加新的行為一樣锄奢,你也可以給自己的協(xié)議定義新的行為。
修改Bird協(xié)議聲明师坎,使之遵守BooleanType協(xié)議:

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

遵守BooleanType協(xié)議意味著你的類型需要有一個(gè)Bool類型的 boolValue屬性。那么是不是意味著你必須給現(xiàn)在或者之后的每一個(gè)Bird類型都添加這么個(gè)屬性呢堪滨?
當(dāng)然胯陋,我們有更容易的辦法———協(xié)議擴(kuò)展。在Bird定義下添加如下代碼:

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

這個(gè)擴(kuò)展讓canFly屬性代表了每一個(gè)Bird類型的Boolean 值遏乔。playground中添加如下代碼发笔,一起來見證奇跡:

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

你會(huì)看到"I can fly!" 出現(xiàn)在輔助編輯器上。但是這里你僅是在if 語(yǔ)句中使用了UnladenSwallow捻激。(你可以挖掘更多用法!0贰!)

對(duì)swift標(biāo)準(zhǔn)庫(kù)的影響

你已經(jīng)見證過了協(xié)議擴(kuò)展是多么棒--- 你可以使用它自定義和擴(kuò)展你自己的代碼的能力调俘,還有你APP之外的協(xié)議(標(biāo)準(zhǔn)庫(kù)和第三方框架協(xié)議)旺垒。也許你會(huì)好奇,swift團(tuán)隊(duì)是怎么使用協(xié)議擴(kuò)展來提升swift標(biāo)準(zhǔn)庫(kù)的骇钦。
swift通過向標(biāo)準(zhǔn)庫(kù)中添加諸如map, reduce和filter等高階函數(shù)來提升函數(shù)式編程范式竞漾。這些方法存在不同CollectionType類型中,例如:Array:

["frog", "pants"].map{ $0.length}.reduce(0){$0 + $1}

array調(diào)用map會(huì)返回另一個(gè)array, 新的array調(diào)用reduce來歸納結(jié)果為9.(map會(huì)遍歷數(shù)組中每一個(gè)元素坦仍,并且返回由數(shù)組長(zhǎng)度組成的數(shù)組叨襟,reduce把新數(shù)組中元素長(zhǎng)度做加運(yùn)算)
這種情況下幔荒,map和reduce作為swift標(biāo)準(zhǔn)庫(kù)的一部分被包含在Array中,如果你按下cmd,點(diǎn)擊map右犹,你可以看到它怎么定義的:

extension Array : _ArrayType {
         func map(transform: (T) -> U) -> [U]
 }

這里的map函數(shù)被定義為Array的一個(gè)擴(kuò)展. swift的函數(shù)范式工作范圍遠(yuǎn)不止Array, 它在任意的CollectionType類型上都能工作姚垃。所以swift 1.2下是怎么工作的呢?
按下cmd,點(diǎn)擊Range的map函數(shù)掂墓,你將看到以下定義:

extension Range {
      func map(transform: (T) -> U) -> [U]
}

這些表明在swift 1.2中看成, swift標(biāo)準(zhǔn)庫(kù)中的各種CollectionType類型需要重新定義map函數(shù)。因?yàn)楸M管Array和Range都是CollectionType吃嘿,但是結(jié)構(gòu)體不能被子類化,也沒有統(tǒng)一的實(shí)現(xiàn)亮瓷。
這并不僅僅是swift標(biāo)準(zhǔn)庫(kù)做出的細(xì)微差別贪嫂,也對(duì)你使用swift類型做出約束。
下面的泛型函數(shù)接受一個(gè)遵守Flyable協(xié)議的CollectionType類型參數(shù)斗塘,返回airspeedVelocity最大的元素亮靴。

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

在swift 1.2中沒有協(xié)議擴(kuò)展,這會(huì)出現(xiàn)編譯錯(cuò)誤贞岭。map和reduce函數(shù)只會(huì)存在于預(yù)先定義的類型中搓侄,不會(huì)在任意的CollectionType都會(huì)工作讶踪。
在swift 2.0中有了協(xié)議擴(kuò)展,Array和Range的map定義如下:

extension CollectionType {  
         func map(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

盡管你不能看到map源碼---至少到swift 2開源(目前已經(jīng)開源了)柱查,ColletionType有map的一個(gè)默認(rèn)實(shí)現(xiàn)云石,而且所有的CollectionType都能免費(fèi)獲得??汹忠。
在playground下面添加早先提到的泛型函數(shù):

func topSpeed(c: T) -> Double {   
     return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}

map和reduce函數(shù)可以用在你的Flyable實(shí)例集合中了∫ド牛現(xiàn)在你終于可以回答他們中誰(shuí)是最快的参歹。

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

結(jié)果應(yīng)該無人質(zhì)疑吧??(海燕最快)

延伸閱讀

你可以在這里下載本tutorial完整playground代碼犬庇。
你已經(jīng)見識(shí)過POP的威力了臭挽,創(chuàng)建你自己的簡(jiǎn)單協(xié)議欢峰,然后使用協(xié)議擴(kuò)展來擴(kuò)展他們纽帖。有了默認(rèn)實(shí)現(xiàn)懊直,你可以給已經(jīng)存在的協(xié)議一個(gè)統(tǒng)一的實(shí)現(xiàn)室囊,有些像基類但是優(yōu)于基類盼铁,因?yàn)樗€可以應(yīng)用自愛結(jié)構(gòu)體和枚舉類型上饶火。
另外趁窃,協(xié)議擴(kuò)展不僅能夠勇子啊你自定義的協(xié)議上,還可以擴(kuò)展swift標(biāo)準(zhǔn)庫(kù)裆针,Cocoa, CocoaTouch中協(xié)議的默認(rèn)行為世吨。
如果想要瀏覽swift 2.0令人興奮的新特征呻征,你可以閱讀[our "what's new in swift 2.0 article"], 或者 Apple's swift blog陆赋。
你也可以在蘋果開發(fā)者入口Protocal Oriented Programming觀看WWDC Session深入理解背后的原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗅榕,隨后出現(xiàn)的幾起案子凌那,更是在濱河造成了極大的恐慌案怯,老刑警劉巖嘲碱,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扶欣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)髓绽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來株茶,“玉大人启盛,你說我怎么就攤上這事僵闯」鞒В” “怎么了浦马?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)磺陡。 經(jīng)常有香客問我币他,道長(zhǎng)蝴悉,這世上最難降的妖魔是什么拍冠? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮晃财,結(jié)果婚禮上拓劝,老公的妹妹穿的比我還像新娘。我一直安慰自己栖博,他們只是感情好仇让,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卫玖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陕靠。 梳的紋絲不亂的頭發(fā)上脱茉,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天琴许,我揣著相機(jī)與錄音,去河邊找鬼益兄。 笑死箭券,一個(gè)胖子當(dāng)著我的面吹牛邦鲫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庆捺,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼滔以,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼你画!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拟逮,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤敦迄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后罚屋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脾猛,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沐飘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了睦裳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撼唾。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡倒谷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牵祟,到底是詐尸還是另有隱情抖格,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站滓玖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翩肌。R本人自食惡果不足惜禁悠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一绷蹲、第九天 我趴在偏房一處隱蔽的房頂上張望顾孽。 院中可真熱鬧,春花似錦拦英、人聲如沸疤估。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慷荔。三九已至,卻和暖如春贷岸,著一層夾襖步出監(jiān)牢的瞬間磷雇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崩掘,地道東北人呢堰。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓枉疼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親骂维。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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