21、【Swift】協(xié)議

協(xié)議語法

  • 與類呻澜、結(jié)構(gòu)體递礼、枚舉類型非常相似
protocol SomeProtocol {
    // protocol definition goes here
}

- 表示該類型采納協(xié)議,多個(gè)協(xié)議用逗號分開:

```swift
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}
  • 有父類的寫法羹幸,父類名放協(xié)議名之前脊髓,用逗號分隔
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

屬性要求

  • 場景:要求遵循該協(xié)議的類型,提供特定名字和類型的實(shí)例屬性或類型屬性
  • 特性:
    • 不要求 存儲(chǔ)屬性 or 計(jì)算屬性(只要求名稱和類型一致)
    • 須明確可讀可寫(set 和 get)
      • 可讀寫:不能用常量栅受、只讀屬性
      • 只讀:任意類型
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}
  • 類型屬性: 實(shí)現(xiàn)時(shí)将硝,使用 class 或static 關(guān)鍵字
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}
  • 只有一個(gè)實(shí)例屬性要求
protocol FullyNamed {
    var fullName: String { get }
}
  • 使用
struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "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 is "USS Enterprise"
  • 當(dāng) prefix 值存在時(shí)屏镊,fullName 將 prefix 放在 name 之前以創(chuàng)建星艦的全名

方法要求

  • 使用場景:要求采納的類型依疼,實(shí)現(xiàn)指定的實(shí)例方法和類方法
  • 語法:
    • 與正常實(shí)例、類方法相同而芥,但不需大括號
    • 方法參數(shù)不能定義默認(rèn)值(可在擴(kuò)展中寫默認(rèn)實(shí)現(xiàn))
protocol SomeProtocol {
    static func someTypeMethod()
}
  • 只有一個(gè)實(shí)例方法要求的協(xié)議律罢,要求生成器提供一個(gè)生成隨機(jī)數(shù)的標(biāo)準(zhǔn)過程
protocol RandomNumberGenerator {
    func random() -> Double
}
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())")
// Prints "Here's a random number: 0.37464991998171"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"

異變方法要求

  • 場景:方法需要改變(或異變)其所屬的實(shí)例
  • 語法:在方法的 func 關(guān)鍵字之前,使用 mutating 關(guān)鍵字

在協(xié)議標(biāo)記實(shí)例方法 mutating 棍丐,mutating 只在結(jié)構(gòu)體和枚舉要寫误辑,為類實(shí)現(xiàn)該方法的時(shí)不用寫 mutating

protocol Togglable {
    mutating func 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 is now equal to .on

構(gòu)造器要求

  • 場景:遵循協(xié)議的類型實(shí)現(xiàn)指定的初始化器

  • 語法:不用寫大括號

protocol SomeProtocol {
    init(someParameter: Int)
}

協(xié)議構(gòu)造器要求的類實(shí)現(xiàn)

  • 實(shí)現(xiàn)要求:
    • 指定和便捷初始化器都可以
    • 必須用 required 關(guān)鍵字修飾(保證了子類繼承父類協(xié)議時(shí),實(shí)現(xiàn)方法)
class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

由于 final 的類沒子類歌逢,協(xié)議初始化器實(shí)現(xiàn)的類巾钉,用 final 標(biāo)記,不需用 required 修飾秘案。因?yàn)檫@樣的類不能被繼承子類砰苍。詳見 阻止重寫

  • 一個(gè)子類潦匈,重寫了父類指定的初始化器 + 遵循協(xié)議實(shí)現(xiàn)了要求的初始化器,這個(gè)初始化器的實(shí)現(xiàn)添加 required 和 override
protocol SomeProtocol {
    init()
}
 
class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}
 
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}

可失敗構(gòu)造器要求

  • 可失敗的初始化器的實(shí)現(xiàn):
    • 不可失敗初始化器
    • 隱式展開的可失敗初始化器

協(xié)議作為類型

  • 協(xié)議自身并不實(shí)現(xiàn)功能赚导,可以變?yōu)橐粋€(gè)功能完備的類型在代碼中使用历等。
    • 作為參數(shù)或返回類型
    • 作為常量、變量或者屬性的類型辟癌;
    • 作為數(shù)組寒屯、字典或者其他存儲(chǔ)器的元素的類型

協(xié)議是類型,要開頭大寫(比如說 FullyNamed 和 RandomNumberGenerator )來匹配 Swift 里其他類型名稱格式(比如說 Int 黍少、 String 還有 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
    }
}
  • generator 屬性是 RandomNumberGenerator 類型寡夹,任何采納了RandomNumberGenerator 協(xié)議的類型

  • 初始化器有一個(gè)形式參數(shù)叫做generator ,它同樣也是 RandomNumberGenerator 類型厂置,接收遵循這個(gè)協(xié)議的類型的值

  • random() 方法菩掏,由于 generator 已知采納了RandomNumberGenerator ,它保證了會(huì)有 random() 方法以供調(diào)用

  • Dice 類用 LinearCongurentialGenerator 實(shí)例創(chuàng)建一個(gè)六面骰子的隨機(jī)數(shù)生成器昵济,來創(chuàng)建一個(gè)六面骰子
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
    print("Random dice roll is \(d6.roll())")
}

委托 / 代理

  • 是一種設(shè)計(jì)模式:委托/代理模式
    • 通過定義一個(gè)封裝了委托責(zé)任的協(xié)議來實(shí)現(xiàn)
    • 遵循了協(xié)議的類型(所謂的委托)來保證提供被委托的功能
    • 響應(yīng)一個(gè)特定的行為
    • 從外部資源取回?cái)?shù)據(jù)(而不需要了解資源具體的類型)

Delegation 委托智绸,可能也以“代理”而為人熟知,這里我們選擇譯為“委托”是為了更好的理解避免混淆

  • 定義了兩個(gè)協(xié)議以用于基于骰子的棋盤游戲:
protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}
  • DiceGame 與骰子有關(guān)的游戲采納的協(xié)議访忿,DiceGameDelegate 協(xié)議可以被任何追蹤 DiceGame 進(jìn)度的類型采納

  • 采用 DiceGame 協(xié)議瞧栗;然后通知一個(gè) DiceGameDelegate 關(guān)于進(jìn)度的信息

class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = Array(repeating: 0, count: finalSquare + 1)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}
  • dice 屬性為常量,因它不需初始化后再改變海铆,而且協(xié)議只需它是可讀

  • delegate 屬性為可選 迹恐,自動(dòng)初始化為 nil ,play() 方法調(diào)用委托時(shí)候需用可選鏈

  • DiceGameDelegate 協(xié)議:

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            print("Started a new game of Snakes and Ladders")
        }
        print("The game is using a \(game.dice.sides)-sided dice")
    }
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        numberOfTurns += 1
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(_ game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns

在擴(kuò)展里添加協(xié)議遵循

  • 場景:給已存在的類遵循一個(gè)新協(xié)議(無法訪問該類型的源代碼也行)

類型已經(jīng)存在的實(shí)例卧斟,自動(dòng)地采納和遵循這個(gè)協(xié)議殴边。

  • 用文本表達(dá)的類型實(shí)現(xiàn)
protocol TextRepresentable {
    var textualDescription: String { get }
}
  • 遵循 TextRepresentable :
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}
  • Dice 實(shí)例現(xiàn)在都可以被視作 TextRepresentable :
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints "A 12-sided dice"
extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
print(game.textualDescription)

有條件地遵循協(xié)議

  • 使用場景:泛型有條件遵守協(xié)議
  • 語法:協(xié)議的名字后面寫泛型 where 分句
  • 讓 Array 類型在存儲(chǔ)遵循 TextRepresentable 協(xié)議的元素時(shí)遵循TextRepresentable 協(xié)議
    • 讓元素?fù)碛袇f(xié)議方法
extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// Prints "[A 6-sided dice, A 12-sided dice]"

在擴(kuò)展里聲明采納協(xié)議

  • 場景:實(shí)現(xiàn)了協(xié)議方法,但沒遵循協(xié)議
  • 語法:遵循協(xié)議 + 空拓展
struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
  • Hamster 實(shí)例珍语,可以賦值給遵守 TextRepresentable 協(xié)議的實(shí)例
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// Prints "A hamster named Simon"

類型不會(huì)因?yàn)閷?shí)現(xiàn)協(xié)議方法锤岸,就自動(dòng)遵守協(xié)議,必須顯式地聲明

使用合成實(shí)現(xiàn)來采納協(xié)議

  • 場景:使用系統(tǒng)定義 + 實(shí)現(xiàn)好的協(xié)議板乙,直接調(diào)用協(xié)議的功能方法(減少重復(fù)代碼)

  • 常見系統(tǒng)協(xié)議: Equatable 是偷、 Hashable 以及 Comparable 協(xié)議

  • Swift 為以下自定義類型提供了 Equatable 的綜合實(shí)現(xiàn):

    • 只包含遵循 Equatable 協(xié)議的存儲(chǔ)屬性結(jié)構(gòu)體
    • 只關(guān)聯(lián)遵循 Equatable 協(xié)議的類型的枚舉亡驰;
    • 沒有關(guān)聯(lián)類型的枚舉晓猛。
  • 遵循 Equatable 協(xié)議

    • 不需手動(dòng)實(shí)現(xiàn) == 運(yùn)算符
    • 默認(rèn)的 != 實(shí)現(xiàn)
  • Swift為以下自定義類型提供了 Hashable 的綜合實(shí)現(xiàn):

    • 只包含遵循 Hashable 協(xié)議的存儲(chǔ)屬性的結(jié)構(gòu)體;
    • 只關(guān)聯(lián)遵循 Hashable 協(xié)議的類型的枚舉凡辱;
    • 沒有關(guān)聯(lián)類型的枚舉。
  • 遵循 Hashable 協(xié)議

    • 不需要手動(dòng)實(shí)現(xiàn) hash(into:) 方法
  • Swift為不包含原始值的枚舉栗恩,提供 Comparable 的綜合實(shí)現(xiàn)
    • 如果枚舉擁有關(guān)聯(lián)類型透乾,這些類型必須都遵循 Comparable 協(xié)議
  • 遵循 Comparable 協(xié)議
    • 不需要手動(dòng)實(shí)現(xiàn) <= 、 > 和 >= 運(yùn)算符
  • 定義了一個(gè)包含 beginners、intermediates以及 experts 情況的枚舉SkillLevel

  • Experts 還額外使用數(shù)字來記錄他們擁有的星星數(shù)量等級乳乌。

enum SkillLevel: Comparable {
    case beginner
    case intermediate
    case expert(stars: Int)
}
var levels = [SkillLevel.intermediate, SkillLevel.beginner,
              SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() {
    print(level)
}
// Prints "beginner"
// Prints "intermediate"
// Prints "expert(stars: 3)"
// Prints "expert(stars: 5)"

協(xié)議類型的集合

  • 場景:存儲(chǔ)遵守統(tǒng)一協(xié)議的元素
let things: [TextRepresentable] = [game, d12, simonTheHamster]
  • 現(xiàn)在可以遍歷數(shù)組中的元素了捧韵,并且打印每一個(gè)元素的文本化描述:
for thing in things {
    print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
  • 注意 thing 常量是 TextRepresentable 類型。它不是 Dice 類型汉操,抑或 DiceGame 還是Hamster 再来,就算后臺(tái)實(shí)際類型是它們之一

協(xié)議的繼承

  • 場景:擴(kuò)充功能
  • 語法:與類繼承類似,但可以多繼承
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}
  • 繼承了上邊 TextRepresentable 協(xié)議
protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}
  • SnakesAndLadders 類可以通過擴(kuò)展來采納和遵循 PrettyTextRepresentable :
extension SnakesAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = textualDescription + ":\n"
        for index in 1...finalSquare {
            switch board[index] {
            case let ladder where ladder > 0:
                output += "▲ "
            case let snake where snake < 0:
                output += "▼ "
            default:
                output += "○ "
            }
        }
        return output
    }
}

print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

類專屬的協(xié)議

  • 場景:限制協(xié)議只能被類類型采納(并且不是結(jié)構(gòu)體或者枚舉)
  • 語法:添加 AnyObject 關(guān)鍵字到協(xié)議的繼承列表
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

協(xié)議合成

  • 場景:要求一個(gè)類型一次遵循多個(gè)協(xié)議

  • 注意:協(xié)議組合不定義任何新的協(xié)議類型

  • 語法:用 SomeProtocol & AnotherProtocol 的形式

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)
// Prints "Happy birthday, Malcolm, you're 21!"
  • 不關(guān)心具體是什么樣的類型傳入函數(shù)磷瘤,只要它遵循這兩個(gè)要求的協(xié)議即可
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)
    }
}

// 任何 Location 的子類且遵循 Named 協(xié)議的類型
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!"

檢查協(xié)議一致性

  • 場景:檢查是否遵守協(xié)議
  • 語法:使用 is 和 as
  • 循協(xié)議is運(yùn)算符返回 true 否則返回 false
  • as? 成功芒篷,返回協(xié)議的可選項(xiàng),如果不遵循協(xié)議采缚,返回nil 针炉;
  • as! 成功返回解包值,失敗運(yùn)行時(shí)報(bào)錯(cuò)
protocol HasArea {
    var area: Double { get }
}
  • Circle 和 Country 扳抽,這兩個(gè)類都遵循 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 }
}
  • Animal 的類篡帕,它不遵循 HasArea 協(xié)議
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}
  • Circle 、 Country 和 Animal 類并不基于相同的基類贸呢。不過它們都是類
let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]
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é)議的類型實(shí)現(xiàn)
  • 語法:使用optional 修飾符作為前綴
  • 可選要求協(xié)議被 Objective-C 遵循
    • 協(xié)議和可選要求必須用 @objc 標(biāo)志標(biāo)記
    • @objc 協(xié)議只能被繼承 OC 類或 @objc 類采納(不能被結(jié)構(gòu)體或者枚舉采納)
  • 可選方法或?qū)傩詴r(shí)镰烧,類型自動(dòng)變成可選項(xiàng)
    • 一個(gè) (Int) ->String 類型的方法會(huì)變成 ((Int) -> String)?(函數(shù)類型可選項(xiàng),不是返回值)
  • 可選協(xié)議可在可選鏈中調(diào)用
    • 在調(diào)用方法的時(shí)候在方法名后邊寫一個(gè)問號來檢查它是否被實(shí)現(xiàn)
someOptionalMethod?(someArgument)
  • Counter 的整數(shù)計(jì)數(shù)的類楞陷,用一個(gè)外部數(shù)據(jù)源來提供它的增量
@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

可以寫一個(gè)遵循 CounterDataSource 的自定義類而不實(shí)現(xiàn)任何協(xié)議要求拌滋。反正它們都是可選的。盡管技術(shù)上來講是可以的猜谚,但這樣的話就不能做一個(gè)好的數(shù)據(jù)源了败砂。

  • 定義的 Counter 類,有一個(gè)可選的 dataSource 屬性魏铅,類型是CounterDataSource?
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}
  • dataSource 不是 nil 的時(shí)候才能調(diào)用 incrementForCount(forCount:)昌犹, dataSource 確實(shí)存在,也沒有人能保證它實(shí)現(xiàn)了 incrementForCount(forCount:) 览芳,因?yàn)樗强蛇x要求

  • 就算 dataSource 確實(shí)存在斜姥,也沒有人能保證它實(shí)現(xiàn)了 incrementForCount(forCount:) ,因?yàn)樗强蛇x要求

  • 據(jù)源在每次查詢時(shí)返回固定值 3 .它通過實(shí)現(xiàn)可選 fixedIncrement 屬性要求來實(shí)現(xiàn)這一點(diǎn)

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
  • 使 Counter 實(shí)例依照它當(dāng)前的count 值往上或往下朝著零計(jì)數(shù)
@objc class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
  • 使用 TowardsZeroSource 給現(xiàn)存的 Counter 實(shí)例來從 -4 到零沧竟。一旦計(jì)數(shù)器到零铸敏,就不會(huì)再變化:
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0

協(xié)議擴(kuò)展

  • 場景:通過擴(kuò)展,實(shí)現(xiàn)協(xié)議屬性或方法
  • RandomNumberGenerator 協(xié)議可以擴(kuò)展來提供 randomBool() 方法
extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
  • 有的遵循類型自動(dòng)獲得這個(gè)方法的實(shí)現(xiàn)
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.37464991998171"
print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"

提供默認(rèn)實(shí)現(xiàn)

  • 使用協(xié)議擴(kuò)展悟泵,給協(xié)議方法或者計(jì)算屬性杈笔,提供默認(rèn)實(shí)現(xiàn)
    • 遵守協(xié)議 + 自定義了實(shí)現(xiàn) = 替代默認(rèn)實(shí)現(xiàn)

有默認(rèn)實(shí)現(xiàn)的要求,不需要使用可選鏈就能調(diào)用

  • 提供一個(gè)默認(rèn)實(shí)現(xiàn)來簡單的返回訪問textualDescription 屬性的結(jié)果
extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

為協(xié)議擴(kuò)展添加限制條件

  • 場景:協(xié)議擴(kuò)展糕非,明確遵循類型的限制
  • 語法:在擴(kuò)展協(xié)議名字后邊使用where 分句來寫這些限制
  • 給 Collection 定義一個(gè)擴(kuò)展蒙具,任意元素遵循上面 TextRepresentable 協(xié)議
extension Collection where Iterator.Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
  • 之前的 Hamster 結(jié)構(gòu)體球榆,它遵循 TextRepresentable 協(xié)議, Hamster 值的數(shù)組
let murrayTheHamster = Hamster(name: "Murray")
let morganTheHamster = Hamster(name: "Morgan")
let mauriceTheHamster = Hamster(name: "Maurice")
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
  • Array 遵循 Collection 并且數(shù)組的元素遵循 TextRepresentable 協(xié)議禁筏,數(shù)組可以使用 textualDescription 屬性來獲取它內(nèi)容的文本化
print(hamsters.textualDescription)
// Prints "[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]"

如果遵循類型滿足了為相同方法或者屬性提供實(shí)現(xiàn)的多限制擴(kuò)展的要求持钉,Swift 會(huì)使用最匹配限制的實(shí)現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篱昔,一起剝皮案震驚了整個(gè)濱河市每强,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌州刽,老刑警劉巖空执,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怀伦,居然都是意外死亡脆烟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門房待,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邢羔,“玉大人,你說我怎么就攤上這事桑孩“莺祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵流椒,是天一觀的道長敏簿。 經(jīng)常有香客問我,道長宣虾,這世上最難降的妖魔是什么惯裕? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮绣硝,結(jié)果婚禮上蜻势,老公的妹妹穿的比我還像新娘。我一直安慰自己鹉胖,他們只是感情好握玛,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甫菠,像睡著了一般挠铲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寂诱,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天拂苹,我揣著相機(jī)與錄音,去河邊找鬼刹衫。 笑死醋寝,一個(gè)胖子當(dāng)著我的面吹牛搞挣,可吹牛的內(nèi)容都是我干的带迟。 我是一名探鬼主播音羞,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仓犬!你這毒婦竟也來了嗅绰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤搀继,失蹤者是張志新(化名)和其女友劉穎窘面,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叽躯,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡财边,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了点骑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酣难。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖黑滴,靈堂內(nèi)的尸體忽然破棺而出憨募,到底是詐尸還是另有隱情,我是刑警寧澤袁辈,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布菜谣,位于F島的核電站,受9級特大地震影響晚缩,放射性物質(zhì)發(fā)生泄漏尾膊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一荞彼、第九天 我趴在偏房一處隱蔽的房頂上張望冈敛。 院中可真熱鬧,春花似錦卿泽、人聲如沸莺债。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽齐邦。三九已至,卻和暖如春第租,著一層夾襖步出監(jiān)牢的瞬間措拇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工慎宾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丐吓,地道東北人浅悉。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像券犁,于是被迫代替她去往敵國和親术健。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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

  • 中文文檔 協(xié)議 定義了一個(gè)藍(lán)圖粘衬,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法荞估、屬性,以及其他需要的東西稚新。類勘伺、結(jié)構(gòu)體或枚...
    伯wen閱讀 355評論 0 0
  • 本章將會(huì)介紹 協(xié)議語法屬性要求方法要求(Method Requirements)Mutating 方法要求構(gòu)造器要...
    寒橋閱讀 413評論 0 3
  • swift社區(qū):http://www.swift51.com[http://www.swift51.com](開源...
    白水灬煮一切閱讀 1,938評論 6 6
  • 類、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議褂删,并為協(xié)議定義的這些要求提供具體實(shí)現(xiàn)飞醉。 除了遵循協(xié)議的類型必須實(shí)現(xiàn)的要求外,還可以對...
    xmb閱讀 338評論 0 0
  • 定義:協(xié)議為方法屯阀、屬性缅帘、以及其他特定的任務(wù)需求或功能定義藍(lán)圖。 協(xié)議可被類蹲盘、結(jié)構(gòu)體股毫、或枚舉類型采納以提供所需功能的...
    json_jie閱讀 333評論 0 0