版本記錄
版本號(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ì)注意到Car
和Motorcycle
共享一些功能盗誊,因此您創(chuàng)建了一個(gè)稱為MotorVehicle
的基類并將其添加到其中时甚。然后,Car
和Motorcycle
將從MotorVehicle
繼承哈踱。您還設(shè)計(jì)了一個(gè)名為Aircraft
的基類荒适,Plane
繼承自該基類。
您認(rèn)為开镣,“這很好刀诬。”可是等等邪财!您的賽車(chē)游戲設(shè)定為30XX
年陕壹,有些汽車(chē)可以飛行质欲。
現(xiàn)在,您面臨困境糠馆。 Swift
不支持多重繼承嘶伟。您的飛行汽車(chē)如何從MotorVehicle
和Aircraft
繼承?您是否創(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í)符合MotorVehicle
和Aircraft
的FlyingCar
類丁溅。 整潔吧?
是時(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
,帶有屬性name
和canFly
藏雏。 然后拷况,它定義了一個(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í)符合Bird
和Flyable
協(xié)議舔涎。 它的airspeedVelocity
是一個(gè)包含flappyFrequency
和flappyAmplitude
的計(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
}
}
企鵝Penguin
是Bird
掘而,但它不會(huì)飛挟冠。 好東西,您沒(méi)有采用繼承方法讓所有鳥(niǎo)類都可以飛行(Flyable)
袍睡!
使用協(xié)議知染,您可以定義功能組件并使任何相關(guān)對(duì)象符合它們。
然后斑胜,您聲明SwiftBird
控淡,但是在我們的游戲中有不同版本的SwiftBird
。 version
屬性越高止潘,由計(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遭垛,Penguin
和SwiftBird
中刪除let canFly =...
尼桶。 再次構(gòu)造playground
。 您會(huì)注意到playground
仍然可以成功構(gòu)建锯仪,因?yàn)閰f(xié)議擴(kuò)展現(xiàn)在可以滿足該要求泵督。
Enums Can Play, Too
Swift中的枚舉Enum
類型比C
和C ++
的枚舉功能強(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
符合Bird
和Flyable
這兩個(gè)協(xié)議久窟。 因?yàn)樗沁@樣的遵循者秩冈,所以它也可以使用canFly
的默認(rèn)實(shí)現(xiàn)。
Overriding Default Behavior
您的UnladenSwallow
類型通過(guò)遵循Bird
協(xié)議自動(dòng)收到canFly
的實(shí)現(xiàn)斥扛。 但是入问,您希望UnladenSwallow.unknown
為canFly
返回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í)行操作叁丧。 同樣,reversedSlice
是ReversedCollection <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
子句指定Sequence
的Element
類型必須符合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),僅在處理Sequence
的Racer
類型時(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)方法定義為模型的一部分。 為此棚愤,將Score
和RacingScore
的定義替換為以下內(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)該注意到SwiftBird
的airspeedVelocity
隨著每次增強(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)注~~~