Swift:協(xié)議

中文文檔

協(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)行切換秕磷,用枚舉成員 OnOff 表示。
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)注 requiredoverride 修飾符:
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)與其他類型(例如 IntDouble陨簇,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, 并指定UIViewControllerUITableView的數(shù)據(jù)源和代理, 此時可以使用擴(kuò)展, 將協(xié)議的實(shí)現(xiàn)分離, 使代碼更簡潔

  • 下面是使用擴(kuò)展, 分離UITableViewDataSourceUITableViewDelegate的代碼

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
  • 由于 thingTextRepresentable 類型趟咆,任何 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)明一個需要的父類。

  • 下面的例子中辆亏,將 NamedAged 兩個協(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)換中描述的 isas 操作符來檢查協(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)方法去匹配那個最特殊的限制。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亩鬼,一起剝皮案震驚了整個濱河市殖告,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雳锋,老刑警劉巖黄绩,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異魄缚,居然都是意外死亡宝与,警方通過查閱死者的電腦和手機(jī)焚廊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門冶匹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咆瘟,你說我怎么就攤上這事嚼隘。” “怎么了袒餐?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵飞蛹,是天一觀的道長。 經(jīng)常有香客問我灸眼,道長卧檐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任焰宣,我火速辦了婚禮霉囚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匕积。我一直安慰自己盈罐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布闪唆。 她就那樣靜靜地躺著盅粪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悄蕾。 梳的紋絲不亂的頭發(fā)上票顾,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼奠骄。 笑死霸旗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戚揭。 我是一名探鬼主播诱告,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼民晒!你這毒婦竟也來了精居?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤潜必,失蹤者是張志新(化名)和其女友劉穎靴姿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磁滚,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佛吓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了垂攘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片维雇。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晒他,靈堂內(nèi)的尸體忽然破棺而出吱型,到底是詐尸還是另有隱情,我是刑警寧澤陨仅,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布津滞,位于F島的核電站,受9級特大地震影響灼伤,放射性物質(zhì)發(fā)生泄漏触徐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一狐赡、第九天 我趴在偏房一處隱蔽的房頂上張望撞鹉。 院中可真熱鬧,春花似錦猾警、人聲如沸孔祸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崔慧。三九已至,卻和暖如春穴墅,著一層夾襖步出監(jiān)牢的瞬間惶室,已是汗流浹背温自。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留皇钞,地道東北人悼泌。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像夹界,于是被迫代替她去往敵國和親馆里。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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