Swift3.1_協(xié)議

簡(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é)議一致性

你可以使用isas操作符來(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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胰挑,一起剝皮案震驚了整個(gè)濱河市蔓罚,隨后出現(xiàn)的幾起案子椿肩,更是在濱河造成了極大的恐慌,老刑警劉巖豺谈,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑象,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡茬末,警方通過(guò)查閱死者的電腦和手機(jī)厂榛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)团南,“玉大人噪沙,你說(shuō)我怎么就攤上這事⊥赂” “怎么了正歼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拷橘。 經(jīng)常有香客問(wèn)我局义,道長(zhǎng),這世上最難降的妖魔是什么冗疮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任萄唇,我火速辦了婚禮,結(jié)果婚禮上术幔,老公的妹妹穿的比我還像新娘另萤。我一直安慰自己,他們只是感情好诅挑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布四敞。 她就那樣靜靜地躺著,像睡著了一般拔妥。 火紅的嫁衣襯著肌膚如雪忿危。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天没龙,我揣著相機(jī)與錄音铺厨,去河邊找鬼。 笑死硬纤,一個(gè)胖子當(dāng)著我的面吹牛解滓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筝家,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伐蒂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肛鹏?” 一聲冷哼從身側(cè)響起逸邦,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎在扰,沒(méi)想到半個(gè)月后缕减,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芒珠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年桥狡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皱卓。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裹芝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娜汁,到底是詐尸還是另有隱情嫂易,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布掐禁,位于F島的核電站怜械,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏傅事。R本人自食惡果不足惜缕允,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹭越。 院中可真熱鬧障本,春花似錦、人聲如沸响鹃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茴迁。三九已至寄悯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕义,已是汗流浹背猜旬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倦卖,地道東北人洒擦。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像怕膛,于是被迫代替她去往敵國(guó)和親熟嫩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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