簡(jiǎn)介
規(guī)定了用來(lái)實(shí)現(xiàn)某一特定任務(wù)或者功能的方法陈惰、屬性,以及其他需要的東西禀挫。類旬陡、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議,并為協(xié)議定義的這些要求提供具體實(shí)現(xiàn)语婴。某個(gè)類型能夠滿足某個(gè)協(xié)議的要求描孟,就可以說(shuō)該類型遵循這個(gè)協(xié)議。
定義協(xié)議
協(xié)議的定義方式與類砰左、結(jié)構(gòu)體和枚舉的定義非常相似:
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
要讓自定義類型遵循某個(gè)協(xié)議匿醒,在定義類型時(shí),需要在類型名稱后加上協(xié)議名稱缠导,中間以冒號(hào):
分隔廉羔。遵循多個(gè)協(xié)議時(shí),各協(xié)議之間用逗號(hào),
分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結(jié)構(gòu)體的定義部分
}
擁有父類的類在遵循協(xié)議時(shí)僻造,應(yīng)該將父類名放在協(xié)議名之前憋他,以逗號(hào)分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
屬性要求
協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實(shí)例屬性或類型屬性孩饼。協(xié)議不指定屬性是存儲(chǔ)型屬性還是計(jì)算型屬性,它只指定屬性的名稱和類型竹挡。此外镀娶,協(xié)議還指定屬性是可讀的還是可讀可寫的。
如果協(xié)議要求屬性是可讀可寫的此迅,那么該屬性不能是常量屬性或只讀的計(jì)算型屬性汽畴。如果協(xié)議只要求屬性是可讀的,那么該屬性不僅可以是可讀的耸序,如果代碼需要的話忍些,還可以是可寫的罢坝。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協(xié)議中定義類型屬性時(shí)男应,總是使用static
關(guān)鍵字作為前綴游桩。當(dāng)類類型遵循協(xié)議時(shí)筛峭,除了static
關(guān)鍵字镰吵,還可以使用class
關(guān)鍵字來(lái)聲明類型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
如果一個(gè)類或者結(jié)構(gòu)體遵循了協(xié)議竹握,那么必須包含協(xié)議的屬性和方法:
struct someStruct: SomeProtocol {
// 遵循 SomeProtocol 協(xié)議的屬性
var mustBeSettable: Int
var doesNotNeedToBeSettable: Int
// 自己的屬性
var myString: String
}
方法要求
協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)某些指定的實(shí)例方法或類方法。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中,但是不需要大括號(hào)和方法體。可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同痹兜。但是,不支持為協(xié)議中的方法的參數(shù)提供默認(rèn)值熟尉。
protocol MethodProtocol {
func double(_ num: Int) -> Int
}
正如屬性要求中所述剧包,在協(xié)議中定義類方法的時(shí)候陕贮,總是使用 static
關(guān)鍵字作為前綴。當(dāng)類類型遵循協(xié)議時(shí)艰毒,除了static
關(guān)鍵字,還可以使用class
關(guān)鍵字作為前綴:
protocol StaticMethodProtocol {
static func someTypeMethod()
}
如果一個(gè)類或者結(jié)構(gòu)體或者枚舉遵循了協(xié)議搜囱,那么必須包含協(xié)議的屬性和方法:
struct someStruct: SomeProtocol, MethodProtocol {
// 遵循 SomeProtocol 協(xié)議的屬性
var mustBeSettable: Int
var doesNotNeedToBeSettable: Int
// 自己的屬性
var myString: String
// 遵循 MethodProtocol 協(xié)議的方法實(shí)現(xiàn)
func double(_ num: Int) -> Int {
return num * 2
}
// 自己的方法
func treble(_ num: Int) -> Int {
return num * 3
}
}
Mutating 方法要求
有時(shí)需要在方法中改變方法所屬的實(shí)例丑瞧。例如,在值類型(即結(jié)構(gòu)體和枚舉)的實(shí)例方法中蜀肘,將mutating
關(guān)鍵字作為方法的前綴绊汹,寫在func
關(guān)鍵字之前,表示可以在該方法中修改它所屬的實(shí)例以及實(shí)例的任意屬性的值幌缝。
如果你在協(xié)議中定義了一個(gè)實(shí)例方法灸促,該方法會(huì)改變遵循該協(xié)議的類型的實(shí)例,那么在定義協(xié)議時(shí)需要在方法前加mutating
關(guān)鍵字涵卵。這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求浴栽。
protocol Togglable {
mutating func toggle()
}
當(dāng)使用枚舉或結(jié)構(gòu)體來(lái)實(shí)現(xiàn)Togglable
協(xié)議時(shí),需要提供一個(gè)帶有 mutating
前綴的toggle()
方法轿偎。
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)造器的聲明坏晦,但不需要寫花括號(hào)和構(gòu)造器的實(shí)體:
protocol NameProtocol {
var name: String {set get}
init(name: String)
}
構(gòu)造器要求在類中的實(shí)現(xiàn)
你可以在遵循協(xié)議的類中實(shí)現(xiàn)構(gòu)造器萝玷,無(wú)論是作為指定構(gòu)造器,還是作為便利構(gòu)造器昆婿。無(wú)論哪種情況球碉,你都必須為構(gòu)造器實(shí)現(xiàn)標(biāo)上 required
修飾符:
class Person: NameProtocol {
var name: String
required init(name: String) {
self.name = name
}
}
可失敗構(gòu)造器要求
遵循協(xié)議的類型可以通過(guò)可失敗構(gòu)造器init?
或非可失敗構(gòu)造器init
來(lái)滿足協(xié)議中定義的可失敗構(gòu)造器要求。協(xié)議中定義的非可失敗構(gòu)造器要求可以通過(guò)非可失敗構(gòu)造器init
或隱式解包可失敗構(gòu)造器init!
來(lái)滿足仓蛆。
協(xié)議作為類型
盡管協(xié)議本身并未實(shí)現(xiàn)任何功能睁冬,但是協(xié)議可以被當(dāng)做一個(gè)成熟的類型來(lái)使用。
協(xié)議可以像其他普通類型一樣使用看疙,使用場(chǎng)景如下:
- 作為函數(shù)豆拨、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量、變量或?qū)傩缘念愋?/li>
- 作為數(shù)組能庆、字典或其他容器中的元素類型
protocol TrebleProtocol {
mutating func treble() -> Int
}
class IntNumber: TrebleProtocol {
var num: Int
func treble() -> Int {
return num * 3
}
init(num: Int) {
self.num = num
}
}
class Number {
var trebleNumber: TrebleProtocol
init(trebleNumber: TrebleProtocol) {
self.trebleNumber = trebleNumber
}
}
let intNumber = IntNumber(num: 5)
let number = Number(trebleNumber: intNumber)
print(number.trebleNumber.treble()) // 15
委托(代理)模式
委托是一種設(shè)計(jì)模式施禾,它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實(shí)例。委托模式的實(shí)現(xiàn)很簡(jiǎn)單:定義協(xié)議來(lái)封裝那些需要被委托的功能搁胆,這樣就能確保遵循協(xié)議的類型能提供這些功能弥搞。委托模式可以用來(lái)響應(yīng)特定的動(dòng)作邮绿,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無(wú)需關(guān)心外部數(shù)據(jù)源的類型拓巧。
聲明一個(gè)狗的協(xié)議斯碌,里面有一個(gè)方法:
protocol DogDelegate {
func bark(message: String)
}
Dog
類有一個(gè)可選屬性delegate
,它的類型是DogDelegate
類型肛度。因此在doBark()
方法中通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)調(diào)用它的方法傻唾。若delegate
屬性為nil
,則調(diào)用方法會(huì)優(yōu)雅地失敗承耿,并不會(huì)產(chǎn)生錯(cuò)誤冠骄。
class Dog {
var name: String
var delegate: DogDelegate?
init(name: String) {
self.name = name
}
func doBark() {
self.delegate?.bark(message: "汪汪汪,我是小狗\(name)")
}
}
定義Person
類加袋,遵循協(xié)議DogDelegate
凛辣。
class Person: DogDelegate {
var name: String
let dog: Dog = {
return Dog(name: "旺財(cái)")
}()
init(name: String) {
self.name = name
self.dog.delegate = self
}
// 實(shí)現(xiàn)協(xié)議方法
func bark(message: String) {
print("狗對(duì)\(name)說(shuō)的話是: \(message)")
}
}
這樣,通過(guò)DogDelegate
類型的代理职烧,Person
實(shí)例就可以獲取到Dog
的叫的內(nèi)容:
let jay = Person(name: "Jay")
jay.dog.doBark()
// 狗對(duì)Jay說(shuō)的話是: 汪汪汪扁誓,我是小狗旺財(cái)
解決循環(huán)引用
但是這樣做是有問(wèn)題的,jay
強(qiáng)引用dog
蚀之,dog
強(qiáng)引用delegate
蝗敢,然而delegate
又強(qiáng)引用jay
,造成循環(huán)引用導(dǎo)致內(nèi)存泄露:
你需要把delegate
弱引用:
protocol DogDelegate: class {
func bark(message: String)
}
class Dog {
var name: String
weak var delegate: DogDelegate?
init(name: String) {
self.name = name
}
func doBark() {
self.delegate?.bark(message: "汪汪汪足删,我是小狗\(name)")
}
}
通過(guò)擴(kuò)展添加協(xié)議一致性
即便無(wú)法修改源代碼寿谴,依然可以通過(guò)擴(kuò)展令已有類型遵循并符合協(xié)議。擴(kuò)展可以為已有類型添加屬性失受、方法讶泰、下標(biāo)以及構(gòu)造器,因此可以符合協(xié)議中的相應(yīng)要求拂到。
定義一個(gè)數(shù)值三倍的協(xié)議:
protocol TrebleProtocol {
mutating func treble()
}
讓Int
的拓展和Double
的拓展遵循協(xié)議痪署,實(shí)現(xiàn)協(xié)議方法:
extension Int:TrebleProtocol {
mutating func treble() {
self = self * 3
}
}
extension Double: TrebleProtocol {
mutating func treble() {
self = self * 3
}
}
Int
類型的變量和Double
類型的變量都能調(diào)用treble()
方法讓自身變成三倍:
var num = 3
num.treble() // num = 9
var float = 2.5
float.treble() // num = 7.5
協(xié)議類型的集合
協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用,前提是數(shù)組或字典內(nèi)的元素都遵循協(xié)議:
let numbers: [TrebleProtocol] = [1, 3, 5, 1.9]
for item in numbers {
var trebleItem = item
trebleItem.treble()
print(trebleItem)
}
// 3
// 9
// 15
// 5.7
協(xié)議的繼承
協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議兄旬,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求惠桃。協(xié)議的繼承語(yǔ)法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:
protocol MultipleProtocol: TrebleProtocol {
mutating func double()
}
遵循MultipleProtocol
協(xié)議的對(duì)象就相當(dāng)于同時(shí)遵循了TrebleProtocol
協(xié)議:
class Number: MultipleProtocol {
var value = 0
init(value: Int) {
self.value = value
}
func double() {
value *= 2
}
func treble() {
value *= 3
}
}
Number
實(shí)例調(diào)用double()
方法讓屬性value
變成當(dāng)前值的兩倍辖试,調(diào)用treble()
方法讓value
變成當(dāng)前值的三倍:
let number = Number(value: 3)
number.double()
print(number.value) // 6
number.treble()
print(number.value) // 18
類類型專屬協(xié)議
你可以在協(xié)議的繼承列表中,通過(guò)添加class
關(guān)鍵字來(lái)限制協(xié)議只能被類類型遵循劈狐,而結(jié)構(gòu)體或枚舉不能遵循該協(xié)議罐孝。class
關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前:
protocol SomeClassOnlyProtocol: class {
// 這里是類類型專屬協(xié)議的定義部分
}
當(dāng)協(xié)議定義的要求需要遵循協(xié)議的類型必須是引用語(yǔ)義而非值語(yǔ)義時(shí)肥缔,應(yīng)該采用類類型專屬協(xié)議莲兢。
協(xié)議合成
有時(shí)候需要同時(shí)遵循多個(gè)協(xié)議,你可以將多個(gè)協(xié)議采用&
這樣的格式進(jìn)行組合,稱為 協(xié)議合成protocol composition
改艇。
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!”
檢查協(xié)議一致性
你可以使用is
和as
操作符來(lái)檢查協(xié)議一致性收班,即是否符合某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型谒兄。檢查和轉(zhuǎn)換到某個(gè)協(xié)議類型在語(yǔ)法上和類型的檢查和轉(zhuǎn)換完全相同:
-
is
用來(lái)檢查實(shí)例是否符合某個(gè)協(xié)議摔桦,若符合則返回true
,否則返回false
承疲。 -
as?
返回一個(gè)可選值邻耕,當(dāng)實(shí)例符合某個(gè)協(xié)議時(shí),返回類型為協(xié)議類型的可選值燕鸽,否則返回nil
兄世。 -
as!
將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個(gè)協(xié)議類型,如果強(qiáng)轉(zhuǎn)失敗啊研,會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤御滩。
可選的協(xié)議要求
協(xié)議可以定義可選要求,遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求党远。在協(xié)議中使用optional
關(guān)鍵字作為前綴來(lái)定義可選要求削解。可選要求用在你需要和Objective-C
打交道的代碼中麸锉。協(xié)議和可選要求都必須帶上@objc
屬性钠绍。標(biāo)記@objc
特性的協(xié)議只能被繼承自 Objective-C
類的類或者@objc
類遵循,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議花沉。
協(xié)議擴(kuò)展
協(xié)議可以通過(guò)擴(kuò)展來(lái)為遵循協(xié)議的類型提供屬性柳爽、方法以及下標(biāo)的實(shí)現(xiàn)。通過(guò)這種方式碱屁,你可以基于協(xié)議本身來(lái)實(shí)現(xiàn)這些功能磷脯,而無(wú)需在每個(gè)遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn),也無(wú)需使用全局函數(shù)娩脾。
提供默認(rèn)實(shí)現(xiàn)
可以通過(guò)協(xié)議擴(kuò)展來(lái)為協(xié)議要求的屬性赵誓、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)。如果遵循協(xié)議的類型為這些要求提供了自己的實(shí)現(xiàn)柿赊,那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用俩功。
為協(xié)議擴(kuò)展添加限制條件
在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制條件碰声,只有遵循協(xié)議的類型滿足這些限制條件時(shí)诡蜓,才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。