Swift編程思想(三) —— 基于Swift5.1的面向協(xié)議編程(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2020.02.14 星期五

前言

Swift作為一門(mén)開(kāi)發(fā)語(yǔ)言,它也有自己的特點(diǎn)和對(duì)應(yīng)的編程特點(diǎn)夫凸,接下來(lái)我們就一起看一下這門(mén)語(yǔ)言衰抑。讓我們一起熟悉和學(xué)習(xí)它姿搜。感興趣的可以看下面幾篇。
1. Swift編程思想(一) —— 函數(shù)式編程簡(jiǎn)介(一)
2. Swift編程思想(二) —— 函數(shù)式編程簡(jiǎn)介(二)

開(kāi)始

首先看下主要內(nèi)容

在此面向協(xié)議的編程教程中我注,您將學(xué)習(xí)extensions按咒,默認(rèn)實(shí)現(xiàn)和其他將抽象添加到代碼中的技術(shù)。

下面看下寫(xiě)作環(huán)境

Swift 5, iOS 13, Xcode 11

協(xié)議Protocols是Swift的基本功能。 它們?cè)赟wift標(biāo)準(zhǔn)庫(kù)的結(jié)構(gòu)中起著主導(dǎo)作用励七,并且是一種常見(jiàn)的抽象方法智袭。 它們?yōu)槟承┢渌Z(yǔ)言提供的接口提供了類似的體驗(yàn)。

本教程將向您介紹稱為面向協(xié)議的編程(protocol-oriented programming)的軟件工程實(shí)踐掠抬,這已成為Swift的基礎(chǔ)吼野。 如果您正在學(xué)習(xí)Swift,這確實(shí)是您需要掌握的東西两波!

在本教程中瞳步,您將了解:

  • 面向?qū)ο蟮木幊毯兔嫦騾f(xié)議的編程之間的區(qū)別。
  • 具有默認(rèn)實(shí)現(xiàn)的協(xié)議腰奋。
  • 擴(kuò)展Swift標(biāo)準(zhǔn)庫(kù)单起。
  • 使用泛型進(jìn)一步擴(kuò)展協(xié)議。

你在等什么劣坊? 是時(shí)候啟動(dòng)您的Swift引擎了嘀倒!

假設(shè)您正在開(kāi)發(fā)賽車(chē)視頻游戲。您希望玩家能夠駕駛汽車(chē)局冰,騎摩托車(chē)和駕駛飛機(jī)测蘑。他們甚至可以騎不同的鳥(niǎo)(因?yàn)檫@是視頻游戲),您可以隨心所欲地駕駛康二!這里的關(guān)鍵是可以驅(qū)動(dòng)或操縱許多不同的“事物”碳胳。

此類應(yīng)用程序的一種常見(jiàn)方法是面向?qū)ο蟮木幊蹋梢栽谄渲蟹庋b所有邏輯沫勿,然后將其繼承給其他類挨约。基類中將包含“駕駛”和“飛行員”邏輯藕帜。

您可以通過(guò)為每種車(chē)輛創(chuàng)建類來(lái)開(kāi)始對(duì)游戲進(jìn)行編程√陶郑現(xiàn)在在鳥(niǎo)概念中使用大頭針惜傲。稍后您將進(jìn)行處理洽故。

在編寫(xiě)代碼時(shí),您會(huì)注意到CarMotorcycle共享一些功能盗誊,因此您創(chuàng)建了一個(gè)稱為MotorVehicle的基類并將其添加到其中时甚。然后,CarMotorcycle將從MotorVehicle繼承哈踱。您還設(shè)計(jì)了一個(gè)名為Aircraft的基類荒适,Plane繼承自該基類。

您認(rèn)為开镣,“這很好刀诬。”可是等等邪财!您的賽車(chē)游戲設(shè)定為30XX年陕壹,有些汽車(chē)可以飛行质欲。

現(xiàn)在,您面臨困境糠馆。 Swift不支持多重繼承嘶伟。您的飛行汽車(chē)如何從MotorVehicleAircraft繼承?您是否創(chuàng)建另一個(gè)合并了兩個(gè)功能的基類又碌?可能不是九昧,因?yàn)闆](méi)有干凈簡(jiǎn)便的方法可以做到這一點(diǎn)。

誰(shuí)能從這場(chǎng)災(zāi)難性的困境中拯救您的賽車(chē)游戲毕匀?面向協(xié)議的編程可以解救铸鹰!


Why Protocol-Oriented Programming?

協(xié)議允許您將相似的方法,函數(shù)和屬性分組皂岔。 Swift可讓您在類掉奄,結(jié)構(gòu)和枚舉類型上指定這些接口保證。 只有class類型可以使用基類和繼承凤薛。

Swift中協(xié)議的優(yōu)點(diǎn)是對(duì)象可以遵循多種協(xié)議姓建。

以這種方式編寫(xiě)應(yīng)用程序時(shí),您的代碼將變得更加模塊化缤苫。 將協(xié)議視為功能的構(gòu)建塊速兔。 通過(guò)使對(duì)象符合協(xié)議來(lái)添加新功能時(shí),您無(wú)需構(gòu)建全新的對(duì)象活玲。 那很費(fèi)時(shí)間涣狗。 而是,添加不同的構(gòu)造塊舒憾,直到對(duì)象準(zhǔn)備就緒為止镀钓。

將基類轉(zhuǎn)換為協(xié)議可以解決您的視頻游戲難題。 使用協(xié)議镀迂,您可以創(chuàng)建同時(shí)符合MotorVehicleAircraftFlyingCar類丁溅。 整潔吧?

是時(shí)候動(dòng)手實(shí)踐一下這個(gè)賽車(chē)概念了探遵。


Hatching the Egg

首先打開(kāi)Xcode窟赏,然后創(chuàng)建一個(gè)名為SwiftProtocols.playground的新playground。 然后添加以下代碼:

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

protocol Flyable {
  var airspeedVelocity: Double { get }
}

使用Command-Shift-Return建立playground箱季,以確保其正確編譯涯穷。

這段代碼定義了一個(gè)簡(jiǎn)單的協(xié)議Bird,帶有屬性namecanFly藏雏。 然后拷况,它定義了一個(gè)名為Flyable的協(xié)議,該協(xié)議具有airspeedVelocity屬性。

在以前的協(xié)議時(shí)代赚瘦,開(kāi)發(fā)人員將以Flyable作為基類開(kāi)始最疆,然后依靠對(duì)象繼承來(lái)定義Bird和其他任何飛行的事物。

但是在面向協(xié)議的編程中蚤告,一切都從協(xié)議開(kāi)始努酸。 此技術(shù)使您可以封裝函數(shù)概念,而無(wú)需基類杜恰。

如您所見(jiàn)获诈,這使整個(gè)系統(tǒng)在定義類型時(shí)更加靈活。


Defining Protocol-Conforming Types

首先將以下結(jié)構(gòu)定義添加到playground的底部:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true

  var airspeedVelocity: Double {
    3 * flappyFrequency * flappyAmplitude
  }
}

該代碼定義了一個(gè)新的名為FlappyBird的結(jié)構(gòu)心褐,該結(jié)構(gòu)同時(shí)符合BirdFlyable協(xié)議舔涎。 它的airspeedVelocity是一個(gè)包含flappyFrequencyflappyAmplitude的計(jì)算屬性。 由于不穩(wěn)定逗爹,它會(huì)為canFly返回true亡嫌。

接下來(lái),將以下兩個(gè)結(jié)構(gòu)定義添加到playground的底部

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

struct SwiftBird: Bird, Flyable {
  var name: String { "Swift \(version)" }
  let canFly = true
  let version: Double
  private var speedFactor = 1000.0
  
  init(version: Double) {
    self.version = version
  }

  // Swift is FASTER with each version!
  var airspeedVelocity: Double {
    version * speedFactor
  }
}

企鵝PenguinBird掘而,但它不會(huì)飛挟冠。 好東西,您沒(méi)有采用繼承方法讓所有鳥(niǎo)類都可以飛行(Flyable)袍睡!

使用協(xié)議知染,您可以定義功能組件并使任何相關(guān)對(duì)象符合它們。

然后斑胜,您聲明SwiftBird控淡,但是在我們的游戲中有不同版本的SwiftBirdversion屬性越高止潘,由計(jì)算屬性定義的airspeedVelocity越快掺炭。

但是,您會(huì)看到有冗余凭戴。 每種類型的Bird都必須聲明其是否可以飛行canFly-即使系統(tǒng)中已經(jīng)存在Flyable的概念涧狮。 幾乎就像您需要一種定義協(xié)議方法的默認(rèn)實(shí)現(xiàn)的方法一樣。 嗯簇宽,這就是協(xié)議擴(kuò)展的地方勋篓。


Extending Protocols With Default Implementations

協(xié)議擴(kuò)展允許您定義協(xié)議的默認(rèn)行為吧享。 要實(shí)現(xiàn)第一個(gè)魏割,請(qǐng)?jiān)?code>Bird協(xié)議定義下面插入以下內(nèi)容:

extension Bird {
  // Flyable birds can fly!
  var canFly: Bool { self is Flyable }
}

這段代碼定義了Bird的擴(kuò)展。 它將canFly的默認(rèn)行為設(shè)置為在類型符合Flyable協(xié)議時(shí)返回true钢颂。 換句話說(shuō)钞它,任何Flyable可飛鳥(niǎo)都不再需要顯式聲明它可以canFly。 它會(huì)像大多數(shù)鳥(niǎo)類一樣飛翔。

現(xiàn)在從FlappyBird遭垛,PenguinSwiftBird中刪除let canFly =...尼桶。 再次構(gòu)造playground。 您會(huì)注意到playground仍然可以成功構(gòu)建锯仪,因?yàn)閰f(xié)議擴(kuò)展現(xiàn)在可以滿足該要求泵督。


Enums Can Play, Too

Swift中的枚舉Enum類型比CC ++的枚舉功能強(qiáng)大得多。 它們采用許多傳統(tǒng)上僅支持類或結(jié)構(gòu)類型的功能庶喜,這意味著它們可以符合協(xié)議小腊。

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!")
    }
  }
}

通過(guò)定義正確的屬性,UnladenSwallow符合BirdFlyable這兩個(gè)協(xié)議久窟。 因?yàn)樗沁@樣的遵循者秩冈,所以它也可以使用canFly的默認(rèn)實(shí)現(xiàn)。


Overriding Default Behavior

您的UnladenSwallow類型通過(guò)遵循Bird協(xié)議自動(dòng)收到canFly的實(shí)現(xiàn)斥扛。 但是入问,您希望UnladenSwallow.unknowncanFly返回false

您可以覆蓋默認(rèn)實(shí)現(xiàn)嗎稀颁? 你打賭 回到playground的盡頭并添加一些新代碼:

extension UnladenSwallow {
  var canFly: Bool {
    self != .unknown
  }
}

現(xiàn)在芬失,只有.african.european才能為canFly返回true。 試試看匾灶! 在playground的末尾添加以下代碼:

UnladenSwallow.unknown.canFly         // false
UnladenSwallow.african.canFly         // true
Penguin(name: "King Penguin").canFly  // false

構(gòu)建playground麸折,您會(huì)注意到它顯示了上面評(píng)論中給出的值。

這樣粘昨,您就可以像在面向?qū)ο缶幊讨惺褂锰摂M方法(virtual methods)那樣覆蓋屬性和方法垢啼。


Extending Protocols

您還可以使自己的協(xié)議與Swift標(biāo)準(zhǔn)庫(kù)中的其他協(xié)議保持一致,并定義默認(rèn)行為张肾。 將您的Bird協(xié)議聲明替換為以下代碼:

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

extension CustomStringConvertible where Self: Bird {
  var description: String {
    canFly ? "I can fly" : "Guess I'll just sit here :["
  }
}

符合CustomStringConvertible意味著您的類型需要具有description屬性芭析,以便在需要時(shí)將其自動(dòng)轉(zhuǎn)換為String。 您沒(méi)有定義此屬性到當(dāng)前和將來(lái)的每種Bird類型吞瞪,而是定義了協(xié)議擴(kuò)展馁启,CustomStringConvertible僅將其與Bird類型相關(guān)聯(lián)。

playground底部輸入以下內(nèi)容進(jìn)行嘗試:

UnladenSwallow.african

構(gòu)建playground芍秆,您應(yīng)該會(huì)在助手編輯器中看到I can fly的字樣惯疙。 恭喜你! 您已經(jīng)擴(kuò)展了協(xié)議妖啥。


Effects on the Swift Standard Library

協(xié)議擴(kuò)展無(wú)法用外殼抓一磅重的椰子霉颠,但是您已經(jīng)知道,它們可以提供一種自定義和擴(kuò)展命名類型功能的有效方法荆虱。 Swift團(tuán)隊(duì)還采用協(xié)議來(lái)改進(jìn)Swift標(biāo)準(zhǔn)庫(kù)蒿偎。

將此代碼添加到playground的末尾:

let numbers = [10, 20, 30, 40, 50, 60]
let slice = numbers[1...3]
let reversedSlice = slice.reversed()

let answer = reversedSlice.map { $0 * 10 }
print(answer)

您也許能夠猜出答案朽们,但是可能令人驚訝的是所涉及的類型。

例如诉位,slice不是Array <Int>骑脱,而是ArraySlice <Int>。 這種特殊的包裝器類型充當(dāng)原始數(shù)組的視圖苍糠,提供了一種快速有效的方法來(lái)對(duì)較大數(shù)組的各個(gè)部分執(zhí)行操作叁丧。 同樣,reversedSliceReversedCollection <ArraySlice <Int >>岳瞭,這是另一種包裝器類型歹袁,具有對(duì)原始數(shù)組的視圖。

幸運(yùn)的是寝优,開(kāi)發(fā)Swift標(biāo)準(zhǔn)庫(kù)的向?qū)?code>map函數(shù)定義為Sequence協(xié)議的擴(kuò)展条舔,所有Collection類型都遵循該協(xié)議。 這使您可以像在ReversedCollection上一樣輕松地在Array上調(diào)用map乏矾,而不會(huì)注意到差異孟抗。 您很快就會(huì)借用這一重要的設(shè)計(jì)模式。


Off to the Races

到目前為止钻心,您已經(jīng)定義了幾種符合Bird的類型凄硼。 現(xiàn)在,您將在playground的盡頭添加完全不同的內(nèi)容:

class Motorcycle {
  init(name: String) {
    self.name = name
    speed = 200.0
  }

  var name: String
  var speed: Double
}

這個(gè)類與鳥(niǎo)類或飛行無(wú)關(guān)捷沸。 您只想將摩托車(chē)與企鵝競(jìng)賽摊沉。 現(xiàn)在該將這些古怪的賽車(chē)手帶入起跑線了。


Bringing It All Together

為了統(tǒng)一這些不同的類型痒给,您需要一個(gè)通用的賽車(chē)協(xié)議说墨。 得益于一種稱為追溯建模(retroactive modeling)的好主意,您甚至可以在不觸及原始模型定義的情況下進(jìn)行管理苍柏。 只需將以下內(nèi)容添加到您的playground

// 1
protocol Racer {
  var speed: Double { get }  // speed is the only thing racers care about
}

// 2
extension FlappyBird: Racer {
  var speed: Double {
    airspeedVelocity
  }
}

extension SwiftBird: Racer {
  var speed: Double {
    airspeedVelocity
  }
}

extension Penguin: Racer {
  var speed: Double {
    42  // full waddle speed
  }
}

extension UnladenSwallow: Racer {
  var speed: Double {
    canFly ? airspeedVelocity : 0.0
  }
}

extension Motorcycle: Racer {}

// 3
let racers: [Racer] =
  [UnladenSwallow.african,
   UnladenSwallow.european,
   UnladenSwallow.unknown,
   Penguin(name: "King Penguin"),
   SwiftBird(version: 5.1),
   FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0),
   Motorcycle(name: "Giacomo")]

這是這樣做的:

  • 1) 首先尼斧,定義協(xié)議Racer。 該協(xié)議定義了您的游戲中可以競(jìng)爭(zhēng)的所有內(nèi)容试吁。
  • 2) 然后棺棵,使所有內(nèi)容符合Racer,以便我們所有現(xiàn)有的類型都可以進(jìn)行比賽熄捍。 某些類型(例如Motorcycle)微不足道烛恤。 其他,例如UnladenSwallow余耽,則需要更多邏輯缚柏。 無(wú)論哪種方式,當(dāng)您完成后宾添,都會(huì)有許多一致的Racer類型船惨。
  • 3) 在所有類型都位于開(kāi)始位置的情況下柜裸,您現(xiàn)在創(chuàng)建一個(gè)Array <Racer>缕陕,其中包含您所創(chuàng)建的每種類型的實(shí)例粱锐。

構(gòu)建playground檢查所有編譯。


Top Speed

是時(shí)候編寫(xiě)一個(gè)確定賽車(chē)手最高速度的函數(shù)了扛邑。 將以下代碼添加到playground的末尾:

func topSpeed(of racers: [Racer]) -> Double {
  racers.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
}

topSpeed(of: racers) // 5100

該函數(shù)使用Swift標(biāo)準(zhǔn)庫(kù)函數(shù)max來(lái)找到速度最高的賽車(chē)并返回怜浅。 如果用戶為賽車(chē)手傳入一個(gè)空數(shù)組,則返回0.0蔬崩。

建造playground恶座,您會(huì)發(fā)現(xiàn)您先前創(chuàng)建的賽車(chē)手的最大速度確實(shí)為5100


Making It More Generic

假設(shè)Racers相當(dāng)大沥阳,并且您只想找到一部分參與者的最高速度跨琳。 解決方案是將topSpeed(of :)更改為采用Sequence而不是具體Array的任何東西。

用以下函數(shù)替換現(xiàn)有的topSpeed(of :)實(shí)現(xiàn):

// 1
func topSpeed<RacersType: Sequence>(of racers: RacersType) -> Double
    /*2*/ where RacersType.Iterator.Element == Racer {
  // 3
  racers.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
}

這可能看起來(lái)有點(diǎn)嚇人桐罕,但是它是如何分解的:

  • 1) RacersType是此函數(shù)的通用類型脉让。 它可以是符合Swift標(biāo)準(zhǔn)庫(kù)的Sequence協(xié)議的任何類型。
  • 2) where子句指定SequenceElement類型必須符合Racer協(xié)議才能使用此功能功炮。
  • 3) 實(shí)際的函數(shù)主體與以前相同溅潜。

現(xiàn)在,將以下代碼添加到playground的底部:

topSpeed(of: racers[1...3]) // 42

建立playground薪伏,您將看到輸出為42的答案滚澜。 該函數(shù)現(xiàn)在適用于任何Sequence類型,包括ArraySlice嫁怀。


Making It More Swifty

這是一個(gè)秘密:您可以做得更好设捐。 在`playground的結(jié)尾添加以下內(nèi)容:

extension Sequence where Iterator.Element == Racer {
  func topSpeed() -> Double {
    self.max(by: { $0.speed < $1.speed })?.speed ?? 0.0
  }
}

racers.topSpeed()        // 5100
racers[1...3].topSpeed() // 42

Swift標(biāo)準(zhǔn)庫(kù)劇本中借用,您現(xiàn)在擴(kuò)展了Sequence本身塘淑,使其具有topSpeed()函數(shù)挡育。 該函數(shù)很容易發(fā)現(xiàn),僅在處理SequenceRacer類型時(shí)才適用朴爬。


Protocol Comparators

Swift協(xié)議的另一個(gè)功能是如何表示運(yùn)算符要求即寒,例如==的對(duì)象相等,或如何比較><的對(duì)象召噩。 您已知道這筆交易-將以下代碼添加到playground的底部:

protocol Score {
  var value: Int { get }
}

struct RacingScore: Score {
  let value: Int
}

擁有Score協(xié)議意味著您可以編寫(xiě)以相同方式對(duì)待所有分?jǐn)?shù)的代碼母赵。 但是,通過(guò)使用不同的具體類型(例如RacingScore)具滴,您不會(huì)將這些分?jǐn)?shù)與樣式分?jǐn)?shù)或可愛(ài)分?jǐn)?shù)混為一談凹嘲。 謝謝,編譯器构韵!

您希望分?jǐn)?shù)具有可比性周蹭,這樣您就可以分辨出誰(shuí)得分最高趋艘。 在Swift 3之前,開(kāi)發(fā)人員需要添加全局運(yùn)算符功能以符合這些協(xié)議凶朗。 今天瓷胧,您可以將這些靜態(tài)方法定義為模型的一部分。 為此棚愤,將ScoreRacingScore的定義替換為以下內(nèi)容:

protocol Score: Comparable {
  var value: Int { get }
}

struct RacingScore: Score {
  let value: Int
  
  static func <(lhs: RacingScore, rhs: RacingScore) -> Bool {
    lhs.value < rhs.value
  }
}

真好搓萧! 您已經(jīng)將RacingScore的所有邏輯封裝在一個(gè)地方。 Comparable只需要您為小于運(yùn)算符提供一個(gè)實(shí)現(xiàn)宛畦。 其余要比較的運(yùn)算符(例如大于)具有Swift標(biāo)準(zhǔn)庫(kù)基于小于運(yùn)算符提供的默認(rèn)實(shí)現(xiàn)瘸洛。

playground底部使用以下代碼行測(cè)試新發(fā)現(xiàn)的操作符技能:

RacingScore(value: 150) >= RacingScore(value: 130) // true

建立playground,您會(huì)注意到答案是true次和。 您現(xiàn)在可以比較分?jǐn)?shù)了反肋!


Mutating Functions

到目前為止,您實(shí)現(xiàn)的每個(gè)示例都演示了如何添加函數(shù)踏施。 但是石蔗,如果您想讓協(xié)議定義一些可以改變對(duì)象外觀的東西,該怎么辦读规? 您可以通過(guò)在協(xié)議中使用可變方法來(lái)做到這一點(diǎn)抓督。

playground的底部,添加以下新協(xié)議:

protocol Cheat {
  mutating func boost(_ power: Double)
}

這定義了一種協(xié)議束亏,可使您的類型作弊铃在。 怎么樣? 通過(guò)增加您認(rèn)為合適的任何東西碍遍。

接下來(lái)定铜,使用以下代碼在SwiftBird上創(chuàng)建一個(gè)符合Cheat的擴(kuò)展:

extension SwiftBird: Cheat {
  mutating func boost(_ power: Double) {
    speedFactor += power
  }
}

在這里,您實(shí)現(xiàn)boost(_ :)并通過(guò)傳入的power使speedFactor增加怕敬。您添加了mutating關(guān)鍵字揣炕,以使該結(jié)構(gòu)體知道其值之一將在此函數(shù)中更改。

將以下代碼添加到playground上东跪,以了解其工作原理:

var swiftBird = SwiftBird(version: 5.0)
swiftBird.boost(3.0)
swiftBird.airspeedVelocity // 5015
swiftBird.boost(3.0)
swiftBird.airspeedVelocity // 5030

在這里畸陡,您已經(jīng)創(chuàng)建了一個(gè)可變的SwiftBird,并將其速度提高了三倍虽填,然后又提高了三倍丁恭。 構(gòu)建playground,您應(yīng)該注意到SwiftBirdairspeedVelocity隨著每次增強(qiáng)而增加斋日。

要繼續(xù)學(xué)習(xí)有關(guān)協(xié)議的更多信息牲览,請(qǐng)閱讀Swift的官方文檔official Swift documentation

您可以在Apple的開(kāi)發(fā)人員門(mén)戶上觀看有關(guān)面向協(xié)議的編程的WWDC精彩會(huì)議an excellent WWDC session恶守。 它提供了對(duì)所有背后理論的深入探索第献。

與任何編程范例一樣贡必,很容易變得過(guò)于旺盛并將其用于所有事物。 克里斯·艾德霍夫(Chris Eidhof)的這篇有趣的博客文章blog post by Chris Eidhof提醒讀者庸毫,他們應(yīng)該提防銀子彈解決方案仔拟。 不要在各處僅因?yàn)閰f(xié)議“而使用”。

后記

本篇主要講述了基于Swift5.1的面向協(xié)議編程岔绸,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(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)離奇詭異,居然都是意外死亡挂脑,警方通過(guò)查閱死者的電腦和手機(jī)藕漱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崭闲,“玉大人肋联,你說(shuō)我怎么就攤上這事〉蠹螅” “怎么了橄仍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)牍戚。 經(jīng)常有香客問(wèn)我侮繁,道長(zhǎng),這世上最難降的妖魔是什么如孝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任宪哩,我火速辦了婚禮,結(jié)果婚禮上第晰,老公的妹妹穿的比我還像新娘锁孟。我一直安慰自己,他們只是感情好茁瘦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布品抽。 她就那樣靜靜地躺著,像睡著了一般腹躁。 火紅的嫁衣襯著肌膚如雪桑包。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天纺非,我揣著相機(jī)與錄音哑了,去河邊找鬼赘方。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弱左,可吹牛的內(nèi)容都是我干的窄陡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拆火,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跳夭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起们镜,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤币叹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后模狭,有當(dāng)?shù)厝嗽跇?shù)林里發(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
  • 文/蒙蒙 一蠢莺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧零如,春花似錦躏将、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至肖卧,卻和暖如春蚯窥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工拦赠, 沒(méi)想到剛下飛機(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)容