協(xié)議 定義了一個藍(lán)圖,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法佑力、屬性,以及其他需要的東西筋遭。類打颤、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議,并為協(xié)議定義的這些要求提供具體實(shí)現(xiàn)漓滔。某個類型能夠滿足某個協(xié)議的要求编饺,就可以說該類型遵循這個協(xié)議。
除了遵循協(xié)議的類型必須實(shí)現(xiàn)的要求外响驴,還可以對協(xié)議進(jìn)行擴(kuò)展透且,通過擴(kuò)展來實(shí)現(xiàn)一部分要求或者實(shí)現(xiàn)一些附加功能,這樣遵循協(xié)議的類型就能夠使用這些功能豁鲤。
一秽誊、協(xié)議語法
- 協(xié)議的定義方式與類、結(jié)構(gòu)體和枚舉的定義非常相似:
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
- 要讓自定義類型遵循某個協(xié)議琳骡,在定義類型時锅论,需要在類型名稱后加上協(xié)議名稱,中間以冒號(
:
)分隔楣号。遵循多個協(xié)議時最易,各協(xié)議之間用逗號(,
)分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結(jié)構(gòu)體的定義部分
}
- 擁有父類的類在遵循協(xié)議時,應(yīng)該將父類名放在協(xié)議名之前竖席,以逗號分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
二耘纱、屬性要求
協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實(shí)例屬性或類型屬性敬肚。
協(xié)議不指定屬性是存儲型屬性還是計算型屬性毕荐,它只指定屬性的名稱和類型。
此外艳馒,協(xié)議還指定屬性是可讀的還是可讀可寫的憎亚。
如果協(xié)議要求屬性是可讀可寫的员寇,那么該屬性不能是常量屬性或只讀的計算型屬性。
如果協(xié)議只要求屬性是可讀的第美,那么該屬性不僅可以是可讀的蝶锋,如果代碼需要的話,還可以是可寫的什往。
- 協(xié)議總是用
var
關(guān)鍵字來聲明變量屬性扳缕,在類型聲明后加上{ set get }
來表示屬性是可讀可寫的,可讀屬性則用{ get }
來表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
- 在協(xié)議中定義類型屬性時别威,總是使用
static
關(guān)鍵字作為前綴躯舔。當(dāng)類類型遵循協(xié)議時,除了static
關(guān)鍵字省古,還可以使用class
關(guān)鍵字來聲明類型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
- 如下所示粥庄,這是一個只含有一個實(shí)例屬性要求的協(xié)議:
protocol FullyNamed {
var fullName: String { get }
}
- 下面是一個遵循
FullyNamed
協(xié)議的簡單結(jié)構(gòu)體:
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed"
- 下面是一個更為復(fù)雜的類,它適配并遵循了
FullyNamed
協(xié)議:
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 是 "USS Enterprise"
三豺妓、方法要求(Method Requirements)
協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)某些指定的實(shí)例方法或類方法惜互。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中琳拭,但是不需要大括號和方法體训堆。
可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同白嘁。但是蔫慧,不支持為協(xié)議中的方法的參數(shù)提供默認(rèn)值。
正如屬性要求中所述权薯,在協(xié)議中定義類方法的時候姑躲,總是使用
static
關(guān)鍵字作為前綴。當(dāng)類類型遵循協(xié)議時盟蚣,除了static
關(guān)鍵字黍析,還可以使用class
關(guān)鍵字作為前綴:
protocol SomeProtocol {
static func someTypeMethod()
}
- 下面的例子定義了一個只含有一個實(shí)例方法的協(xié)議:
protocol RandomNumberGenerator {
func random() -> Double
}
- 如下所示,下邊是一個遵循并符合
RandomNumberGenerator
協(xié)議的類屎开。該類實(shí)現(xiàn)了一個叫做 線性同余生成器 的偽隨機(jī)數(shù)算法阐枣。
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”
四、Mutating 方法要求
有時需要在方法中改變(或異變)方法所屬的實(shí)例奄抽。
例如蔼两,在值類型(即結(jié)構(gòu)體和枚舉)的實(shí)例方法中,將
mutating
關(guān)鍵字作為方法的前綴逞度,寫在func
關(guān)鍵字之前额划,表示可以在該方法中修改它所屬的實(shí)例以及實(shí)例的任意屬性的值。如果你在協(xié)議中定義了一個實(shí)例方法档泽,該方法會改變遵循該協(xié)議的類型的實(shí)例俊戳,那么在定義協(xié)議時需要在方法前加
mutating
關(guān)鍵字揖赴。這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求。
注意
實(shí)現(xiàn)協(xié)議中的mutating
方法時抑胎,若是類類型燥滑,則不用寫mutating
關(guān)鍵字。而對于結(jié)構(gòu)體和枚舉,則必須寫mutating
關(guān)鍵字。
- 如下所示淆游,toggle() 方法在定義的時候,使用 mutating 關(guān)鍵字標(biāo)記羽历,這表明當(dāng)它被調(diào)用時,該方法將會改變遵循協(xié)議的類型的實(shí)例:
protocol Togglable {
mutating func toggle()
}
- 下面定義了一個名為
OnOffSwitch
的枚舉淡喜。這個枚舉在兩種狀態(tài)之間進(jìn)行切換秕磷,用枚舉成員On
和Off
表示。
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 現(xiàn)在的值為 .on
五炼团、構(gòu)造器要求
- 協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)指定的構(gòu)造器澎嚣。你可以像編寫普通構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明瘟芝,但不需要寫花括號和構(gòu)造器的實(shí)體:
protocol SomeProtocol {
init(someParameter: Int)
}
1易桃、協(xié)議構(gòu)造器要求的類實(shí)現(xiàn)
- 你可以在遵循協(xié)議的類中實(shí)現(xiàn)構(gòu)造器,無論是作為指定構(gòu)造器锌俱,還是作為便利構(gòu)造器晤郑。無論哪種情況,你都必須為構(gòu)造器實(shí)現(xiàn)標(biāo)上
required
修飾符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
- 使用
required
修飾符可以確保所有子類也必須提供此構(gòu)造器實(shí)現(xiàn)贸宏,從而也能符合協(xié)議造寝。
注意
如果類已經(jīng)被標(biāo)記為final
,那么不需要在協(xié)議構(gòu)造器的實(shí)現(xiàn)中使用required
修飾符吭练,因?yàn)?final
類不能有子類诫龙。
- 如果一個子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個協(xié)議的要求鲫咽,那么該構(gòu)造器的實(shí)現(xiàn)需要同時標(biāo)注
required
和override
修飾符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因?yàn)樽裱瓍f(xié)議签赃,需要加上 required
// 因?yàn)槔^承自父類,需要加上 override
required override init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
2分尸、可失敗構(gòu)造器要求
協(xié)議還可以為遵循協(xié)議的類型定義可失敗構(gòu)造器要求
遵循協(xié)議的類型可以通過可失敗構(gòu)造器(
init?
)或非可失敗構(gòu)造器(init
)來滿足協(xié)議中定義的可失敗構(gòu)造器要求锦聊。協(xié)議中定義的非可失敗構(gòu)造器要求可以通過非可失敗構(gòu)造器(init
)或隱式解包可失敗構(gòu)造器(init!
)來滿足。
protocol SomeClassPtorocol {
init?()
}
class SomeClass {
init() {}
}
class SubSomeClass: SomeClass {
required override init() {}
}
六箩绍、協(xié)議作為類型
盡管協(xié)議本身并未實(shí)現(xiàn)任何功能孔庭,但是協(xié)議可以被當(dāng)做一個成熟的類型來使用。
協(xié)議可以像其他普通類型一樣使用伶选,使用場景如下:
作為函數(shù)史飞、方法或構(gòu)造器中的參數(shù)類型或返回值類型
作為常量尖昏、變量或?qū)傩缘念愋?作為數(shù)組仰税、字典或其他容器中的元素類型
注意
協(xié)議是一種類型构资,因此協(xié)議類型的名稱應(yīng)與其他類型(例如Int
,Double
陨簇,String
)的寫法相同吐绵,使用大寫字母開頭的駝峰式寫法
- 下面是將協(xié)議作為類型使用的例子
protocol RandomNumberGenerator {
func random() -> Double
}
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
- 下面的例子展示了如何使用 LinearCongruentialGenerator 的實(shí)例作為隨機(jī)數(shù)生成器來創(chuàng)建一個六面骰子:
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
七、在擴(kuò)展里添加協(xié)議遵循
- 即便無法修改源代碼河绽,依然可以通過擴(kuò)展令已有類型遵循并符合協(xié)議己单。擴(kuò)展可以為已有類型添加屬性、方法耙饰、下標(biāo)以及構(gòu)造器纹笼,因此可以符合協(xié)議中的相應(yīng)要求。
注意
通過擴(kuò)展令已有類型遵循并符合協(xié)議時苟跪,該類型的所有實(shí)例也會隨之獲得協(xié)議中定義的各項(xiàng)功能廷痘。
例如在開發(fā)中, 在
UIViewController
中使用了UITableView
, 并指定UIViewController
為UITableView
的數(shù)據(jù)源和代理, 此時可以使用擴(kuò)展, 將協(xié)議的實(shí)現(xiàn)分離, 使代碼更簡潔下面是使用擴(kuò)展, 分離
UITableViewDataSource
和UITableViewDelegate
的代碼
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
- 例如下面這個
TextRepresentable
協(xié)議,任何想要通過文本表示一些內(nèi)容的類型都可以實(shí)現(xiàn)該協(xié)議件已。這些想要表示的內(nèi)容可以是實(shí)例本身的描述笋额,也可以是實(shí)例當(dāng)前狀態(tài)的文本描述:
protocol TextRepresentable {
var textualDescription: String { get }
}
- 可以通過擴(kuò)展,令先前提到的
Dice
類遵循并符合TextRepresentable
協(xié)議:
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
- 現(xiàn)在所有
Dice
的實(shí)例都可以看做TextRepresentable
類型:
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// 打印 “A 12-sided dice”
八篷扩、在擴(kuò)展里聲明采納協(xié)議
- 當(dāng)一個類型已經(jīng)符合了某個協(xié)議中的所有要求兄猩,卻還沒有聲明采納該協(xié)議時,可以通過空擴(kuò)展體的擴(kuò)展采納該協(xié)議:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
注意
即使?jié)M足了協(xié)議的所有要求鉴未,類型也不會自動遵循協(xié)議枢冤,必須顯式地遵循協(xié)議。
- 從現(xiàn)在起铜秆,
Hamster
的實(shí)例可以作為TextRepresentable
類型使用:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon”
九掏导、協(xié)議類型的集合
協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用
下面的例子創(chuàng)建了一個元素類型為
TextRepresentable
的數(shù)組:
let things: [TextRepresentable] = [d12, simonTheHamster]
- 如下所示,可以遍歷
things
數(shù)組羽峰,并打印每個元素的文本表示:
for thing in things {
print(thing.textualDescription)
}
// A 12-sided dice
// A hamster named Simon
- 由于
thing
是TextRepresentable
類型趟咆,任何TextRepresentable
的實(shí)例都有一個textualDescription
屬性,所以在每次循環(huán)中可以安全地訪問thing.textualDescription
梅屉。
十值纱、協(xié)議的繼承
- 協(xié)議能夠繼承一個或多個其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求坯汤。協(xié)議的繼承語法與類的繼承相似虐唠,多個被繼承的協(xié)議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協(xié)議的定義部分
}
- 如下所示,
PrettyTextRepresentable
協(xié)議繼承了TextRepresentable
協(xié)議:
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
- 例子中定義了一個新的協(xié)議
PrettyTextRepresentable
惰聂,它繼承自TextRepresentable
協(xié)議疆偿。任何遵循PrettyTextRepresentable
協(xié)議的類型在滿足該協(xié)議的要求時咱筛,也必須滿足TextRepresentable
協(xié)議的要求。在這個例子中杆故,PrettyTextRepresentable
協(xié)議額外要求遵循協(xié)議的類型提供一個返回值為String
類型的prettyTextualDescription
屬性迅箩。
十一、類專屬的協(xié)議
- 你通過添加
AnyObject
關(guān)鍵字到協(xié)議的繼承列表处铛,就可以限制協(xié)議只能被類類型采納(以及非結(jié)構(gòu)體或者非枚舉的類型)饲趋。
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類專屬協(xié)議的定義部分
}
- 在以上例子中,協(xié)議
SomeClassOnlyProtocol
只能被類類型采納撤蟆。如果嘗試讓結(jié)構(gòu)體或枚舉類型采納SomeClassOnlyProtocol
奕塑,則會導(dǎo)致編譯時錯誤。
注意
當(dāng)協(xié)議定義的要求需要遵循協(xié)議的類型必須是引用語義而非值語義時家肯,應(yīng)該采用類類型專屬協(xié)議
十二龄砰、協(xié)議合成
要求一個類型同時遵循多個協(xié)議是很有用的。
你可以使用協(xié)議組合來復(fù)合多個協(xié)議到一個要求里讨衣。
協(xié)議組合行為就和你定義的臨時局部協(xié)議一樣擁有構(gòu)成中所有協(xié)議的需求换棚。
協(xié)議組合不定義任何新的協(xié)議類型。
協(xié)議組合使用
SomeProtocol
&AnotherProtocol
的形式值依。你可以列舉任意數(shù)量的協(xié)議圃泡,用和符號(&
)分開。除了協(xié)議列表愿险,協(xié)議組合也能包含類類型颇蜡,這允許你標(biāo)明一個需要的父類。下面的例子中辆亏,將
Named
和Aged
兩個協(xié)議按照上述語法組合成一個協(xié)議风秤,作為函數(shù)參數(shù)的類型:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”
- 這里有一個例子:將
Location
類和前面的Named
協(xié)議進(jìn)行組合:
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
-
beginConcert(in:)
方法接受一個類型為Location & Named
的參數(shù),這意味著“任何 Location 的子類扮叨,并且遵循 Named 協(xié)議”缤弦。例如,City 就滿足這樣的條件彻磁。
十三碍沐、檢查協(xié)議一致性
- 你可以使用
類型轉(zhuǎn)換
中描述的is
和as
操作符來檢查協(xié)議一致性,即是否符合某協(xié)議衷蜓,并且可以轉(zhuǎn)換到指定的協(xié)議類型累提。檢查和轉(zhuǎn)換到某個協(xié)議類型在語法上和類型的檢查和轉(zhuǎn)換完全相同:
is 用來檢查實(shí)例是否符合某個協(xié)議,若符合則返回 true磁浇,否則返回 false斋陪。
as? 返回一個可選值,當(dāng)實(shí)例符合某個協(xié)議時,返回類型為協(xié)議類型的可選值无虚,否則返回 nil缔赠。
as! 將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個協(xié)議類型,如果強(qiáng)轉(zhuǎn)失敗友题,會引發(fā)運(yùn)行時錯誤嗤堰。
- 下面的例子定義了一個
HasArea
協(xié)議,該協(xié)議定義了一個Double
類型的可讀屬性area
:
protocol HasArea {
var area: Double { get }
}
- 如下所示咆爽,
Circle
類和Country
類都遵循了HasArea
協(xié)議:
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
Circle
類把area
屬性實(shí)現(xiàn)為基于存儲型屬性radius
的計算型屬性梁棠。Country
類則把area
屬性實(shí)現(xiàn)為存儲型屬性置森。這兩個類都正確地符合了HasArea
協(xié)議斗埂。如下所示,
Animal
是一個未遵循HasArea
協(xié)議的類:
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
-
Circle
凫海,Country
呛凶,Animal
并沒有一個共同的基類,盡管如此行贪,它們都是類漾稀,它們的實(shí)例都可以作為AnyObject
類型的值,存儲在同一個數(shù)組中:
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
- 如下所示建瘫,
objects
數(shù)組可以被迭代崭捍,并對迭代出的每一個元素進(jìn)行檢查,看它是否符合HasArea
協(xié)議:
for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
十四啰脚、可選的協(xié)議要求
* 協(xié)議可以定義可選要求殷蛇,遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求。
* 在協(xié)議中使用 `optional` 關(guān)鍵字作為前綴來定義可選要求橄浓。
* 可選要求用在你需要和 Objective-C 打交道的代碼中粒梦。
* 協(xié)議和可選要求都必須帶上 `@objc` 屬性。
* 標(biāo)記 `@objc` 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 `@objc` 類遵循荸实,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議匀们。
- 使用可選要求時(例如,可選的方法或者屬性)准给,它們的類型會自動變成可選的泄朴。比如,一個類型為
(Int) -> String
的方法會變成((Int) -> String)?
露氮。需要注意的是整個函數(shù)類型是可選的祖灰,而不是函數(shù)的返回值。 - 協(xié)議中的可選要求可通過可選鏈?zhǔn)秸{(diào)用來使用沦辙,因?yàn)樽裱瓍f(xié)議的類型可能沒有實(shí)現(xiàn)這些可選要求夫植。類似
someOptionalMethod?(someArgument)
這樣,你可以在可選方法名稱后加上?
來調(diào)用可選方法。 - 下面的例子定義了一個名為
Counter
的用于整數(shù)計數(shù)的類详民,它使用外部的數(shù)據(jù)源來提供每次的增量延欠。數(shù)據(jù)源由CounterDataSource
協(xié)議定義,包含兩個可選要求:
@objc protocol CounterDataSource {
@objc optional func incrementForCount(count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
-
Counter
類含有CounterDataSource?
類型的可選屬性dataSource
沈跨,如下所示:
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
- 下面的例子展示了
CounterDataSource
的簡單實(shí)現(xiàn)由捎。ThreeSource
類遵循了CounterDataSource
協(xié)議,它實(shí)現(xiàn)了可選屬性fixedIncrement
饿凛,每次會返回3
:
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
- 可以使用
ThreeSource
的實(shí)例作為Counter
實(shí)例的數(shù)據(jù)源:
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
十五狞玛、協(xié)議擴(kuò)展
協(xié)議可以通過擴(kuò)展來為遵循協(xié)議的類型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)涧窒。通過這種方式心肪,你可以基于協(xié)議本身來實(shí)現(xiàn)這些功能,而無需在每個遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn)纠吴,也無需使用全局函數(shù)硬鞍。
例如,可以擴(kuò)展
RandomNumberGenerator
協(xié)議來提供randomBool()
方法戴已。該方法使用協(xié)議中定義的random()
方法來返回一個隨機(jī)的Bool
值:
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
- 通過協(xié)議擴(kuò)展固该,所有遵循協(xié)議的類型,都能自動獲得這個擴(kuò)展所增加的方法實(shí)現(xiàn)糖儡,無需任何額外修改:
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”
1伐坏、提供默認(rèn)實(shí)現(xiàn)
- 可以通過協(xié)議擴(kuò)展來為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)握联。如果遵循協(xié)議的類型為這些要求提供了自己的實(shí)現(xiàn)桦沉,那么這些自定義實(shí)現(xiàn)將會替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用。
注意
通過協(xié)議擴(kuò)展為協(xié)議要求提供的默認(rèn)實(shí)現(xiàn)和可選的協(xié)議要求不同拴疤。雖然在這兩種情況下永部,遵循協(xié)議的類型都無需自己實(shí)現(xiàn)這些要求,但是通過擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)可以直接調(diào)用呐矾,而無需使用可選鏈?zhǔn)秸{(diào)用苔埋。
- 例如,
PrettyTextRepresentable
協(xié)議繼承自TextRepresentable
協(xié)議蜒犯,可以為其提供一個默認(rèn)的prettyTextualDescription
屬性组橄,只是簡單地返回textualDescription
屬性的值:
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
2、為協(xié)議擴(kuò)展添加限制條件
在擴(kuò)展協(xié)議的時候罚随,可以指定一些限制條件玉工,只有遵循協(xié)議的類型滿足這些限制條件時,才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)淘菩。這些限制條件寫在協(xié)議名之后遵班,使用
where
子句來描述例如屠升,你可以擴(kuò)展
Collection
協(xié)議,適用于集合中的元素遵循了Equatable
協(xié)議的情況狭郑。通過限制集合元素遵Equatable
協(xié)議腹暖, 作為標(biāo)準(zhǔn)庫的一部分, 你可以使用==
和!=
操作符來檢查兩個元素的等價性和非等價性翰萨。
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
注意
如果一個遵循的類型滿足了為同一方法或?qū)傩蕴峁?shí)現(xiàn)的多個限制型擴(kuò)展的要求脏答, Swift 使用這個實(shí)現(xiàn)方法去匹配那個最特殊的限制。