Swift-協(xié)議

最近項(xiàng)目使用的是OC,后頭看之前用Swift開發(fā)的一個(gè)項(xiàng)目時(shí),發(fā)現(xiàn)很多細(xì)節(jié)都忘記了????。
為了回憶和以后方便查看,現(xiàn)在根據(jù)官方文檔swift編程語言,做下筆記。

1、語法

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 {
    // 這里是類的定義部分
}

2、屬性

協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實(shí)例屬性或類型屬性。協(xié)議不指定屬性是存儲(chǔ)型屬性還是計(jì)算型屬性,它只指定屬性的名稱和類型。此外,協(xié)議還指定屬性是可讀的還是可讀可寫的。

協(xié)議總是用 var 關(guān)鍵字來聲明變量屬性,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的,可讀屬性則用 { get } 來表示

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)鍵字來聲明類型屬性

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

如下所示,這是一個(gè)只含有一個(gè)實(shí)例屬性要求的協(xié)議

protocol FullyNamed {
    var fullName: String { get }
}

FullyNamed 協(xié)議除了要求遵循協(xié)議的類型提供 fullName 屬性外,并沒有其他特別的要求。這個(gè)協(xié)議表示,任何遵循 FullyNamed 的類型,都必須有一個(gè)可讀的 String 類型的實(shí)例屬性 fullName。

下面是一個(gè)遵循 FullyNamed 協(xié)議的簡單結(jié)構(gòu)體

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed"

3、方法

協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)某些指定的實(shí)例方法或類方法。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中,但是不需要大括號(hào)和方法體。可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同。但是,不支持為協(xié)議中的方法的參數(shù)提供默認(rèn)值。

在協(xié)議中定義類方法的時(shí)候,總是使用 static 關(guān)鍵字作為前綴。當(dāng)類類型遵循協(xié)議時(shí),除了 static 關(guān)鍵字,還可以使用 class 關(guān)鍵字作為前綴

protocol SomeProtocol {
    static func someTypeMethod()
}

下面的例子定義了一個(gè)只含有一個(gè)實(shí)例方法的協(xié)議

protocol RandomNumberGenerator {
    func random() -> Double
}

RandomNumberGenerator 協(xié)議要求遵循協(xié)議的類型必須擁有一個(gè)名為 random, 返回值類型為 Double 的實(shí)例方法。

下邊是一個(gè)遵循并符合 RandomNumberGenerator 協(xié)議的類。該類實(shí)現(xiàn)了一個(gè)叫做 線性同余生成器的偽隨機(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”

4、可變方法

有時(shí)需要在方法中改變方法所屬的實(shí)例。例如,在值類型(即結(jié)構(gòu)體和枚舉)的實(shí)例方法中,將 mutating 關(guān)鍵字作為方法的前綴,寫在 func 關(guān)鍵字之前,表示可以在該方法中修改它所屬的實(shí)例以及實(shí)例的任意屬性的值。

注意實(shí)現(xiàn)協(xié)議中的 mutating 方法時(shí),若是類類型,則不用寫 mutating 關(guān)鍵字。而對(duì)于結(jié)構(gòu)體和枚舉,則必須寫 mutating 關(guān)鍵字。

如下所示,Togglable 協(xié)議只要求實(shí)現(xiàn)一個(gè)名為 toggle 的實(shí)例方法。

protocol Togglable {
    mutating func toggle()
}

當(dāng)使用枚舉或結(jié)構(gòu)體來實(shí)現(xiàn) Togglable 協(xié)議時(shí),需要提供一個(gè)帶有 mutating 前綴的 toggle() 方法。

下面定義了一個(gè)名為 OnOffSwitch 的枚舉。這個(gè)枚舉在兩種狀態(tài)之間進(jìn)行切換,用枚舉成員 On 和 Off 表示。枚舉的 toggle() 方法被標(biāo)記為 mutating,以滿足 Togglable 協(xié)議的要求

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

5、構(gòu)造器

協(xié)議可以要求遵循協(xié)議的類型實(shí)現(xiàn)指定的構(gòu)造器。你可以像編寫普通構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號(hào)和構(gòu)造器的實(shí)體

protocol SomeProtocol {
    init(someParameter: Int)
}
  • 構(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è)子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個(gè)協(xié)議的要求,那么該構(gòu)造器的實(shí)現(xiàn)需要同時(shí)標(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)部分
    }
}

6、協(xié)議可作為類型

協(xié)議可以像其他普通類型一樣使用,使用場景如下:
作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
作為常量、變量或?qū)傩缘念愋?br> 作為數(shù)組、字典或其他容器中的元素類型

下面是將協(xié)議作為類型使用的例子

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
    }
}

例子中定義了一個(gè) Dice 類,用來代表桌游中擁有 N 個(gè)面的骰子。Dice 的實(shí)例含有 sides 和 generator 兩個(gè)屬性,前者是整型,用來表示骰子有幾個(gè)面,后者為骰子提供一個(gè)隨機(jī)數(shù)生成器,從而生成隨機(jī)點(diǎn)數(shù)。
generator 屬性的類型為 RandomNumberGenerator,因此任何遵循了 RandomNumberGenerator 協(xié)議的類型的實(shí)例都可以賦值給 generator,除此之外并無其他要求。
Dice 類還有一個(gè)構(gòu)造器,用來設(shè)置初始狀態(tài)。構(gòu)造器有一個(gè)名為 generator,類型為 RandomNumberGenerator 的形參。在調(diào)用構(gòu)造方法創(chuàng)建 Dice 的實(shí)例時(shí),可以傳入任何遵循 RandomNumberGenerator 協(xié)議的實(shí)例給 generator。

下面的例子展示了如何使用 LinearCongruentialGenerator 的實(shí)例作為隨機(jī)數(shù)生成器來創(chuàng)建一個(gè)六面骰子

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

7、代理

它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實(shí)例。委托模式的實(shí)現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能,這樣就能確保遵循協(xié)議的類型能提供這些功能。

下面的例子定義了兩個(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 協(xié)議可以被任意涉及骰子的游戲遵循。DiceGameDelegate 協(xié)議可以被任意類型遵循,用來追蹤 DiceGame 的游戲過程。

下面代碼:SnakesAndLadders使用 Dice 實(shí)例作為骰子,并且實(shí)現(xiàn)了 DiceGame 和 DiceGameDelegate 協(xié)議,后者用來記錄游戲的過程

class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = [Int](count: finalSquare + 1, repeatedValue: 0)
        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)
     }
}

SnakesAndLadders 類中,該類遵循了 DiceGame 協(xié)議,并且提供了相應(yīng)的可讀的 dice 屬性和 play() 方法。( dice 屬性在構(gòu)造之后就不再改變,且協(xié)議只要求 dice 為可讀的,因此將 dice 聲明為常量屬性。
游戲使用 SnakesAndLadders 類的 init() 構(gòu)造器來初始化游戲。所有的游戲邏輯被轉(zhuǎn)移到了協(xié)議中的 play() 方法,play() 方法使用協(xié)議要求的 dice 屬性提供骰子搖出的值。
注意,delegate 并不是游戲的必備條件,因此 delegate 被定義為 DiceGameDelegate 類型的可選屬性。因?yàn)?delegate 是可選值,因此會(huì)被自動(dòng)賦予初始值 nil。隨后,可以在游戲中為 delegate 設(shè)置適當(dāng)?shù)闹怠?br> DicegameDelegate 協(xié)議提供了三個(gè)方法用來追蹤游戲過程。這三個(gè)方法被放置于游戲的邏輯中,即 play() 方法內(nèi)。分別在游戲開始時(shí),新一輪開始時(shí),以及游戲結(jié)束時(shí)被調(diào)用。
因?yàn)?delegate 是一個(gè) DiceGameDelegate 類型的可選屬性,因此在 play() 方法中通過可選鏈?zhǔn)秸{(diào)用來調(diào)用它的方法。若 delegate 屬性為 nil,則調(diào)用方法會(huì)優(yōu)雅地失敗,并不會(huì)產(chǎn)生錯(cuò)誤。若 delegate 不為 nil,則方法能夠被調(diào)用,并傳遞 SnakesAndLadders 實(shí)例作為參數(shù)。

如下示例定義了 DiceGameTracker 類,它遵循了 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")
    }
}

DiceGameTracker 實(shí)現(xiàn)了 DiceGameDelegate 協(xié)議要求的三個(gè)方法,用來記錄游戲已經(jīng)進(jìn)行的輪數(shù)。當(dāng)游戲開始時(shí),numberOfTurns 屬性被賦值為 0,然后在每新一輪中遞增,游戲結(jié)束后,打印游戲的總輪數(shù)。
gameDidStart(:) 方法從 game 參數(shù)獲取游戲信息并打印。game 參數(shù)是 DiceGame 類型而不是 SnakeAndLadders 類型,所以在gameDidStart(:) 方法中只能訪問 DiceGame 協(xié)議中的內(nèi)容。當(dāng)然了,SnakeAndLadders 的方法也可以在類型轉(zhuǎn)換之后調(diào)用。在上例代碼中,通過 is 操作符檢查 game 是否為 SnakesAndLadders 類型的實(shí)例,如果是,則打印出相應(yīng)的消息。
無論當(dāng)前進(jìn)行的是何種游戲,由于 game 符合 DiceGame 協(xié)議,可以確保 game 含有 dice 屬性。因此在 gameDidStart(_:) 方法中可以通過傳入的 game 參數(shù)來訪問 dice 屬性,進(jìn)而打印出 dice 的 sides 屬性的值。

DiceGameTracker 的運(yùn)行情況如下所示

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

8、通過擴(kuò)展添加協(xié)議一致性

即便無法修改源代碼,依然可以通過擴(kuò)展令已有類型遵循并符合協(xié)議。擴(kuò)展可以為已有類型添加屬性、方法、下標(biāo)以及構(gòu)造器,因此可以符合協(xié)議中的相應(yīng)要求。

例如下面這個(gè) 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"
    }
}

通過擴(kuò)展遵循并符合協(xié)議,和在原始定義中遵循并符合協(xié)議的效果完全相同。協(xié)議名稱寫在類型名之后,以冒號(hào)隔開,然后在擴(kuò)展的大括號(hào)內(nèi)實(shí)現(xiàn)協(xié)議要求的內(nèi)容。

現(xiàn)在所有 Dice 的實(shí)例都可以看做 TextRepresentable 類型

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// 打印 “A 12-sided dice”

9、協(xié)議繼承

協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。協(xié)議的繼承語法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 這里是協(xié)議的定義部分
}

如下所示,PrettyTextRepresentable 協(xié)議繼承了 TextRepresentable 協(xié)議

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

例子中定義了一個(gè)新的協(xié)議 PrettyTextRepresentable,它繼承自 TextRepresentable 協(xié)議。任何遵循 PrettyTextRepresentable 協(xié)議的類型在滿足該協(xié)議的要求時(shí),也必須滿足 TextRepresentable 協(xié)議的要求。在這個(gè)例子中,PrettyTextRepresentable 協(xié)議額外要求遵循協(xié)議的類型提供一個(gè)返回值為 String 類型的 prettyTextualDescription 屬性。

如下所示,擴(kuò)展 SnakesAndLadders,使其遵循并符合 PrettyTextRepresentable 協(xié)議

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
    }
}

上述擴(kuò)展令 SnakesAndLadders 遵循了 PrettyTextRepresentable 協(xié)議,并提供了協(xié)議要求的 prettyTextualDescription 屬性。每個(gè) PrettyTextRepresentable 類型同時(shí)也是 TextRepresentable 類型,所以在 prettyTextualDescription 的實(shí)現(xiàn)中,可以訪問 textualDescription 屬性。然后,拼接上了冒號(hào)和換行符。接著,遍歷數(shù)組中的元素,拼接一個(gè)幾何圖形來表示每個(gè)棋盤方格的內(nèi)容

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

10、類類型專屬協(xié)議

你可以在協(xié)議的繼承列表中,通過添加 class 關(guān)鍵字來限制協(xié)議只能被類類型遵循,而結(jié)構(gòu)體或枚舉不能遵循該協(xié)議。class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 這里是類類型專屬協(xié)議的定義部分
}

11、協(xié)議合成

有時(shí)候需要同時(shí)遵循多個(gè)協(xié)議,你可以將多個(gè)協(xié)議采用 SomeProtocol & AnotherProtocol 這樣的格式進(jìn)行組合,稱為 協(xié)議合成(protocol composition)。你可以羅列任意多個(gè)你想要遵循的協(xié)議,以與符號(hào)(&)分隔。

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!”

12、檢查協(xié)議一致性

is 用來檢查實(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ò)誤。

下面的例子定義了一個(gè) HasArea 協(xié)議,該協(xié)議定義了一個(gè) 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 }
}

Animal 是一個(gè)未遵循 HasArea 協(xié)議的類

class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

它們都是類,它們的實(shí)例都可以作為 AnyObject 類型的值,存儲(chǔ)在同一個(gè)數(shù)組中

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

objects 數(shù)組可以被迭代,并對(duì)迭代出的每一個(gè)元素進(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

當(dāng)?shù)龅脑胤?HasArea 協(xié)議時(shí),將 as? 操作符返回的可選值通過可選綁定,綁定到 objectWithArea 常量上。objectWithArea 是 HasArea 協(xié)議類型的實(shí)例,因此 area 屬性可以被訪問和打印。
objects 數(shù)組中的元素的類型并不會(huì)因?yàn)閺?qiáng)轉(zhuǎn)而丟失類型信息,它們?nèi)匀皇?Circle,Country,Animal 類型。然而,當(dāng)它們被賦值給 objectWithArea 常量時(shí),只被視為 HasArea 類型,因此只有 area 屬性能夠被訪問。

13、可選的協(xié)議要求

協(xié)議可以定義可選要求,遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求。在協(xié)議中使用 optional 關(guān)鍵字作為前綴來定義可選要求??蛇x要求用在你需要和 Objective-C 打交道的代碼中。協(xié)議和可選要求都必須帶上@objc屬性。標(biāo)記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 @objc 類遵循,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議。

使用可選要求時(shí)(例如,可選的方法或者屬性),它們的類型會(huì)自動(dòng)變成可選的。比如,一個(gè)類型為 (Int) -> String 的方法會(huì)變成 ((Int) -> String)?。需要注意的是整個(gè)函數(shù)類型是可選的,而不是函數(shù)的返回值。

下面的例子定義了一個(gè)名為 Counter 的用于整數(shù)計(jì)數(shù)的類,它使用外部的數(shù)據(jù)源來提供每次的增量。數(shù)據(jù)源由 CounterDataSource 協(xié)議定義,包含兩個(gè)可選要求

@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,每次會(huì)返回 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

上述代碼新建了一個(gè) Counter 實(shí)例,并將它的數(shù)據(jù)源設(shè)置為一個(gè) ThreeSource 的實(shí)例,然后調(diào)用 increment() 方法四次。和預(yù)期一樣,每次調(diào)用都會(huì)將 count 的值增加 3

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

協(xié)議可以通過擴(kuò)展來為遵循協(xié)議的類型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)。通過這種方式,你可以基于協(xié)議本身來實(shí)現(xiàn)這些功能,而無需在每個(gè)遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn),也無需使用全局函數(shù)

例如,可以擴(kuò)展 RandomNumberGenerator 協(xié)議來提供 randomBool() 方法。該方法使用協(xié)議中定義的 random() 方法來返回一個(gè)隨機(jī)的 Bool 值

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

通過協(xié)議擴(kuò)展,所有遵循協(xié)議的類型,都能自動(dòng)獲得這個(gè)擴(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”
  • 提供默認(rèn)實(shí)現(xiàn)

可以通過協(xié)議擴(kuò)展來為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)。如果遵循協(xié)議的類型為這些要求提供了自己的實(shí)現(xiàn),那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用。

PrettyTextRepresentable 協(xié)議繼承自 TextRepresentable 協(xié)議,可以為其提供一個(gè)默認(rèn)的 prettyTextualDescription 屬性,只是簡單地返回 textualDescription 屬性的值

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription
    }
}
  • 為協(xié)議擴(kuò)展添加限制條件

在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制條件,只有遵循協(xié)議的類型滿足這些限制條件時(shí),才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。這些限制條件寫在協(xié)議名之后,使用 where 子句來描述

你可以擴(kuò)展 CollectionType 協(xié)議,但是只適用于集合中的元素遵循了 TextRepresentable 協(xié)議的情況

extension Collection where Iterator.Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 協(xié)議 定義了一個(gè)藍(lán)圖,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法、屬性,以及其他需要的東西。類、結(jié)構(gòu)體或枚舉都可以遵...
    CDLOG閱讀 483評(píng)論 0 0
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 3,146評(píng)論 2 9
  • 中文文檔 協(xié)議 定義了一個(gè)藍(lán)圖,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法、屬性,以及其他需要的東西。類、結(jié)構(gòu)體或枚...
    伯wen閱讀 430評(píng)論 0 0
  • 本章將會(huì)介紹 協(xié)議語法屬性要求方法要求(Method Requirements)Mutating 方法要求構(gòu)造器要...
    寒橋閱讀 459評(píng)論 0 3
  • 132.轉(zhuǎn)換錯(cuò)誤成可選值 通過轉(zhuǎn)換錯(cuò)誤成一個(gè)可選值,你可以使用 try? 來處理錯(cuò)誤。當(dāng)執(zhí)行try?表達(dá)式時(shí),如果...
    無灃閱讀 1,433評(píng)論 0 3

友情鏈接更多精彩內(nèi)容