在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 協(xié)議
方法 - Methods
關(guān)于 Swift
下標(biāo)腳本(Subscripts)
類和結(jié)構(gòu)體
類型轉(zhuǎn)換(Type Casting)
控制流
析構(gòu)過程(Deinitialization)
集合類型 (Collection Types)
構(gòu)造過程(Initialization)
Optional Chaining
枚舉(Enumerations)
自動引用計數(shù)
繼承(Inheritance)
擴(kuò)展(Extensions)
泛型
字符串和字符(Strings and Characters)
函數(shù)(Functions)
高級運算符
訪問控制
基本運算符
嵌套類型
閉包(Closures)
協(xié)議
屬性 (Properties)

協(xié)議

協(xié)議(Protocol)用于定義完成某項任務(wù)或功能所必須的方法和屬性,協(xié)議實際上并不提供這些功能或任務(wù)的具體實現(xiàn)(Implementation)--而只用來描述這些實現(xiàn)應(yīng)該是什么樣的。類,結(jié)構(gòu)體,枚舉通過提供協(xié)議所要求的方法,屬性的具體實現(xiàn)來采用(adopt)協(xié)議。任意能夠滿足協(xié)議要求的類型被稱為協(xié)議的遵循者。

協(xié)議可以要求其遵循者提供特定的實例屬性,實例方法,類方法,操作符或下標(biāo)腳本等。

協(xié)議的語法

協(xié)議的定義方式與類,結(jié)構(gòu)體,枚舉的定義都非常相似,如下所示:

    protocol SomeProtocol {
        // 協(xié)議內(nèi)容
    }

在類型名稱后加上協(xié)議名稱,中間以冒號:分隔即可實現(xiàn)協(xié)議;實現(xiàn)多個協(xié)議時,各協(xié)議之間用逗號,分隔,如下所示:

    struct SomeStructure: FirstProtocol, AnotherProtocol {
        // 結(jié)構(gòu)體內(nèi)容
    }

如果一個類在含有父類的同時也采用了協(xié)議,應(yīng)當(dāng)把父類放在所有的協(xié)議之前,如下所示:

    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
        // 類的內(nèi)容
    }

對屬性的規(guī)定

協(xié)議可以規(guī)定其遵循者提供特定名稱與類型的實例屬性(instance property)類屬性(type property),而不管其是存儲型屬性(stored property)還是計算型屬性(calculate property)。此外也可以指定屬性是只讀的還是可讀寫的。

如果協(xié)議要求屬性是可讀寫的,那么這個屬性不能是常量存儲型屬性或只讀計算型屬性;如果協(xié)議要求屬性是只讀的(gettable),那么計算型屬性存儲型屬性都能滿足協(xié)議對屬性的規(guī)定,在你的代碼中,即使為只讀屬性實現(xiàn)了寫方法(settable)也依然有效。

協(xié)議中的屬性經(jīng)常被加以var前綴聲明其為變量屬性,在聲明后加上{ set get }來表示屬性是可讀寫的,只讀的屬性則寫作{ get },如下所示:

    protocol SomeProtocol {
        var mustBeSettable : Int { get set }
        var doesNotNeedToBeSettable: Int { get }
    }

如下所示,通常在協(xié)議的定義中使用class前綴表示該屬性為類成員;在枚舉和結(jié)構(gòu)體實現(xiàn)協(xié)議時中,需要使用static關(guān)鍵字作為前綴。

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

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

    protocol FullyNamed {
        var fullName: String { get }
    }

FullyNamed協(xié)議定義了任何擁有fullName的類型。它并不指定具體類型,而只是要求類型必須提供一個fullName。任何FullyNamed類型都得有一個只讀的fullName屬性,類型為String。

如下所示,這是一個實現(xiàn)了FullyNamed協(xié)議的簡單結(jié)構(gòu)體:

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

這個例子中定義了一個叫做Person的結(jié)構(gòu)體,用來表示具有指定名字的人。從第一行代碼中可以看出,它采用了FullyNamed協(xié)議。

Person結(jié)構(gòu)體的每一個實例都有一個叫做fullName,String類型的存儲型屬性,這正好匹配了FullyNamed協(xié)議的要求,也就意味著,Person結(jié)構(gòu)體完整的遵循了協(xié)議。(如果協(xié)議要求未被完全滿足,在編譯時會報錯)

這有一個更為復(fù)雜的類,它采用并實現(xiàn)了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"

Starship類把fullName屬性實現(xiàn)為只讀的計算型屬性。每一個Starship類的實例都有一個名為name的必備屬性和一個名為prefix的可選屬性。 當(dāng)prefix存在時,將prefix插入到name之前來為Starship構(gòu)建fullName,prefix不存在時,則將直接用name構(gòu)建fullName

對方法的規(guī)定

協(xié)議可以要求其遵循者實現(xiàn)某些指定的實例方法類方法。這些方法作為協(xié)議的一部分,像普通的方法一樣清晰的放在協(xié)議的定義中,而不需要大括號和方法體。

注意: 協(xié)議中的方法支持變長參數(shù)(variadic parameter),不支持參數(shù)默認(rèn)值(default value)

如下所示,協(xié)議中類方法的定義與類屬性的定義相似,在協(xié)議定義的方法前置class關(guān)鍵字來表示。當(dāng)在枚舉結(jié)構(gòu)體實現(xiàn)類方法時,需要使用static關(guān)鍵字來代替。

    protocol SomeProtocol {
        class func someTypeMethod()
    }

如下所示,定義了含有一個實例方法的的協(xié)議。

    protocol RandomNumberGenerator {
        func random() -> Double
    }

RandomNumberGenerator協(xié)議要求其遵循者必須擁有一個名為random, 返回值類型為Double的實例方法。 (盡管這里并未指明,但是我們假設(shè)返回值在[0,1]區(qū)間內(nèi))。

RandomNumberGenerator協(xié)議并不在意每一個隨機(jī)數(shù)是怎樣生成的,它只強(qiáng)調(diào)這里有一個隨機(jī)數(shù)生成器。

如下所示,下邊的是一個遵循了RandomNumberGenerator協(xié)議的類。該類實現(xiàn)了一個叫做線性同余生成器(linear congruential generator)的偽隨機(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) % m)
            return lastRandom / m
        }
    }
    let generator = LinearCongruentialGenerator()
    println("Here's a random number: \(generator.random())")
    // 輸出 : "Here's a random number: 0.37464991998171"
    println("And another one: \(generator.random())")
    // 輸出 : "And another one: 0.729023776863283"

對突變方法的規(guī)定

有時不得不在方法中更改實例的所屬類型。在基于值類型(value types)(結(jié)構(gòu)體,枚舉)的實例方法中,將mutating關(guān)鍵字作為函數(shù)的前綴,寫在func之前,表示可以在該方法中修改實例及其屬性的所屬類型。這一過程在Modifyting Value Types from Within Instance Methods章節(jié)中有詳細(xì)描述。

如果協(xié)議中的實例方法打算改變其遵循者實例的類型,那么在協(xié)議定義時需要在方法前加mutating關(guān)鍵字,才能使結(jié)構(gòu)體,枚舉來采用并滿足協(xié)議中對方法的規(guī)定。

注意: 用實現(xiàn)協(xié)議中的mutating方法時,不用寫mutating關(guān)鍵字;用結(jié)構(gòu)體枚舉實現(xiàn)協(xié)議中的mutating方法時,必須寫mutating關(guān)鍵字。

如下所示,Togglable協(xié)議含有名為toggle的突變實例方法。根據(jù)名稱推測,toggle方法應(yīng)該是用于切換或恢復(fù)其遵循者實例或其屬性的類型。

    protocol Togglable {
        mutating func toggle()
    }

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

如下所示,OnOffSwitch枚舉遵循Togglable協(xié)議,On,Off兩個成員用于表示當(dāng)前狀態(tài)。枚舉的toggle方法被標(biāo)記為mutating,用以匹配Togglabel協(xié)議的規(guī)定。

    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)造器的規(guī)定

協(xié)議可以要求它的遵循類型實現(xiàn)特定的構(gòu)造器。你可以像書寫普通的構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的需求,但不需要寫花括號和構(gòu)造器的實體:

    protocol SomeProtocol {
        init(someParameter: Int)
    }

協(xié)議構(gòu)造器規(guī)定在類中的實現(xiàn)

你可以在遵循該協(xié)議的類中實現(xiàn)構(gòu)造器,并指定其為類的特定構(gòu)造器或者便捷構(gòu)造器。在這兩種情況下,你都必須給構(gòu)造器實現(xiàn)標(biāo)上"required"修飾符:

    class SomeClass: SomeProtocol {
        required init(someParameter: Int) {
            //構(gòu)造器實現(xiàn)
        }
    }

使用required修飾符可以保證:所有的遵循該協(xié)議的子類,同樣能為構(gòu)造器規(guī)定提供一個顯式的實現(xiàn)或繼承實現(xiàn)。

關(guān)于required構(gòu)造器的更多內(nèi)容,請參考The Basics

注意

如果類已經(jīng)被“final”修飾符所標(biāo)示,你就不需要在協(xié)議構(gòu)造器規(guī)定的實現(xiàn)中使用"required"修飾符。因為final類不能有子類。關(guān)于final修飾符的更多內(nèi)容,請參見initialization

如果一個子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器遵循了某個協(xié)議的規(guī)定,那么該構(gòu)造器的實現(xiàn)需要被同時標(biāo)示requiredoverride修飾符

    protocol SomeProtocol {
        init()
    }
    class SomeSuperClass {
        init() {
            //協(xié)議定義
        }
    }
    class SomeSubClass: SomeSuperClass, SomeProtocol {
        // "required" from SomeProtocol conformance; "override" from SomeSuperClass
        required override init() {
            // 構(gòu)造器實現(xiàn)
        }
    }

可失敗構(gòu)造器的規(guī)定

可以通過給協(xié)議Protocols中添加可失敗構(gòu)造器來使遵循該協(xié)議的類型必須實現(xiàn)該可失敗構(gòu)造器。

如果在協(xié)議中定義一個可失敗構(gòu)造器,則在遵頊該協(xié)議的類型中必須添加同名同參數(shù)的可失敗構(gòu)造器或非可失敗構(gòu)造器。 如果在協(xié)議中定義一個非可失敗構(gòu)造器,則在遵循該協(xié)議的類型中必須添加同名同參數(shù)的非可失敗構(gòu)造器或隱式解析類型的可失敗構(gòu)造器(init!)。

協(xié)議類型

盡管協(xié)議本身并不實現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做類型來使用。

使用場景:

  • 協(xié)議類型作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
  • 協(xié)議類型作為常量、變量或?qū)傩缘念愋?/li>
  • 協(xié)議類型作為數(shù)組、字典或其他容器中的元素類型

注意: 協(xié)議是一種類型,因此協(xié)議類型的名稱應(yīng)與其他類型(Int,Double,String)的寫法相同,使用駝峰式寫法

如下所示,這個示例中將協(xié)議當(dāng)做類型來使用

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

例子中又一個Dice類,用來代表桌游中的擁有N個面的骰子。Dice的實例含有sidesgenerator兩個屬性,前者是整型,用來表示骰子有幾個面,后者為骰子提供一個隨機(jī)數(shù)生成器。

generator屬性的類型為RandomNumberGenerator,因此任何遵循了RandomNumberGenerator協(xié)議的類型的實例都可以賦值給generator,除此之外,無其他要求。

Dice類中也有一個構(gòu)造器(initializer),用來進(jìn)行初始化操作。構(gòu)造器中含有一個名為generator,類型為RandomNumberGenerator的形參。在調(diào)用構(gòu)造方法時創(chuàng)建Dice的實例時,可以傳入任何遵循RandomNumberGenerator協(xié)議的實例給generator。

Dice類也提供了一個名為roll的實例方法用來模擬骰子的面值。它先使用generatorrandom方法來創(chuàng)建一個[0-1]區(qū)間內(nèi)的隨機(jī)數(shù)種子,然后加工這個隨機(jī)數(shù)種子生成骰子的面值。generator被認(rèn)為是遵循了RandomNumberGenerator的類型,因而保證了random方法可以被調(diào)用。

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

    var d6 = Dice(sides: 6,generator: LinearCongruentialGenerator())
    for _ in 1...5 {
        println("Random dice roll is \(d6.roll())")
    }
    //輸出結(jié)果
    //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

委托(代理)模式

委托是一種設(shè)計模式(譯者注: 想起了那年 UITableViewDelegate 中的奔跑,那是我逝去的Objective-C。。。),它允許結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能交由(委托)給其他的類型的實例。

委托模式的實現(xiàn)很簡單: 定義協(xié)議封裝那些需要被委托的函數(shù)和方法, 使其遵循者擁有這些被委托的函數(shù)和方法。

委托模式可以用來響應(yīng)特定的動作或接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無需要知道外部數(shù)據(jù)源的所屬類型(譯者注:只要求外部數(shù)據(jù)源遵循某協(xié)議)。

下文是兩個基于骰子游戲的協(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é)議可以在任意含有骰子的游戲中實現(xiàn),DiceGameDelegate協(xié)議可以用來追蹤DiceGame的游戲過程

如下所示,SnakesAndLaddersSnakes and Ladders(譯者注:Control Flow章節(jié)有該游戲的詳細(xì)介紹)游戲的新版本。新版本使用Dice作為骰子,并且實現(xiàn)了DiceGameDiceGameDelegate協(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é)議,并且提供了dice屬性和play實例方法用來遵循協(xié)議。(dice屬性在構(gòu)造之后就不在改變,且協(xié)議只要求dice為只讀的,因此將dice聲明為常量屬性。)

SnakesAndLadders類的構(gòu)造器(initializer)初始化游戲。所有的游戲邏輯被轉(zhuǎn)移到了play方法中,play方法使用協(xié)議規(guī)定的dice屬性提供骰子搖出的值。

注意:delegate并不是游戲的必備條件,因此delegate被定義為遵循DiceGameDelegate協(xié)議的可選屬性,delegate使用nil作為初始值。

DicegameDelegate協(xié)議提供了三個方法用來追蹤游戲過程。被放置于游戲的邏輯中,即play()方法內(nèi)。分別在游戲開始時,新一輪開始時,游戲結(jié)束時被調(diào)用。

因為delegate是一個遵循DiceGameDelegate的可選屬性,因此在play()方法中使用了可選鏈來調(diào)用委托方法。 若delegate屬性為nil, 則delegate所調(diào)用的方法失效。若delegate不為nil,則方法能夠被調(diào)用

如下所示,DiceGameTracker遵循了DiceGameDelegate協(xié)議

    class DiceGameTracker: DiceGameDelegate {
        var numberOfTurns = 0
        func gameDidStart(game: DiceGame) {
            numberOfTurns = 0
            if game is SnakesAndLadders {
                println("Started a new game of Snakes and Ladders")
            }
            println("The game is using a \(game.dice.sides)-sided dice")
        }
        func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
            ++numberOfTurns
            println("Rolled a \(diceRoll)")
        }
        func gameDidEnd(game: DiceGame) {
            println("The game lasted for \(numberOfTurns) turns")
        }
    }

DiceGameTracker實現(xiàn)了DiceGameDelegate協(xié)議規(guī)定的三個方法,用來記錄游戲已經(jīng)進(jìn)行的輪數(shù)。 當(dāng)游戲開始時,numberOfTurns屬性被賦值為0; 在每新一輪中遞加; 游戲結(jié)束后,輸出打印游戲的總輪數(shù)。

gameDidStart方法從game參數(shù)獲取游戲信息并輸出。game在方法中被當(dāng)做DiceGame類型而不是SnakeAndLadders類型,所以方法中只能訪問DiceGame協(xié)議中的成員。當(dāng)然了,這些方法也可以在類型轉(zhuǎn)換之后調(diào)用。在上例代碼中,通過is操作符檢查game是否為 SnakesAndLadders類型的實例,如果是,則打印出相應(yīng)的內(nèi)容。

無論當(dāng)前進(jìn)行的是何種游戲,game都遵循DiceGame協(xié)議以確保game含有dice屬性,因此在gameDidStart方法中可以通過傳入的game參數(shù)來訪問dice屬性,進(jìn)而打印出dicesides屬性的值。

DiceGameTracker的運行情況,如下所示:

    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é)議成員

即便無法修改源代碼,依然可以通過擴(kuò)展(Extension)來擴(kuò)充已存在類型(譯者注: 類,結(jié)構(gòu)體,枚舉等)。擴(kuò)展可以為已存在的類型添加屬性方法,下標(biāo)腳本,協(xié)議等成員。詳情請在擴(kuò)展章節(jié)中查看。

注意: 通過擴(kuò)展為已存在的類型遵循協(xié)議時,該類型的所有實例也會隨之添加協(xié)議中的方法

TextRepresentable協(xié)議含有一個asText,如下所示:

    protocol TextRepresentable {
        func asText() -> String
    }

通過擴(kuò)展為上一節(jié)中提到的Dice類遵循TextRepresentable協(xié)議

    extension Dice: TextRepresentable {
        func asText() -> String {
            return "A \(sides)-sided dice"
        }
    }

從現(xiàn)在起,Dice類型的實例可被當(dāng)作TextRepresentable類型:

    let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator())
    println(d12.asText())
    // 輸出 "A 12-sided dice"

SnakesAndLadders類也可以通過擴(kuò)展的方式來遵循協(xié)議:

    extension SnakesAndLadders: TextRepresentable {
        func asText() -> String {
            return "A game of Snakes and Ladders with \(finalSquare) squares"
        }
    }
    println(game.asText())
    // 輸出 "A game of Snakes and Ladders with 25 squares"

通過擴(kuò)展補充協(xié)議聲明

當(dāng)一個類型已經(jīng)實現(xiàn)了協(xié)議中的所有要求,卻沒有聲明時,可以通過擴(kuò)展來補充協(xié)議聲明:

    struct Hamster {
        var name: String
        func asText() -> String {
            return "A hamster named \(name)"
        }
    }
    extension Hamster: TextRepresentable {}

從現(xiàn)在起,Hamster的實例可以作為TextRepresentable類型使用

    let simonTheHamster = Hamster(name: "Simon")
    let somethingTextRepresentable: TextRepresentable = simonTheHamster
    println(somethingTextRepresentable.asText())
    // 輸出 "A hamster named Simon"

注意: 即使?jié)M足了協(xié)議的所有要求,類型也不會自動轉(zhuǎn)變,因此你必須為它做出明顯的協(xié)議聲明

集合中的協(xié)議類型

協(xié)議類型可以被集合使用,表示集合中的元素均為協(xié)議類型:

    let things: [TextRepresentable] = [game,d12,simonTheHamster]

如下所示,things數(shù)組可以被直接遍歷,并調(diào)用其中元素的asText()函數(shù):

    for thing in things {
        println(thing.asText())
    }
    // A game of Snakes and Ladders with 25 squares
    // A 12-sided dice
    // A hamster named Simon

thing被當(dāng)做是TextRepresentable類型而不是Dice,DiceGameHamster等類型。因此能且僅能調(diào)用asText方法

協(xié)議的繼承

協(xié)議能夠繼承一到多個其他協(xié)議。語法與類的繼承相似,多個協(xié)議間用逗號,分隔

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
        // 協(xié)議定義
    }

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

    protocol PrettyTextRepresentable: TextRepresentable {
        func asPrettyText() -> String
    }

遵循PrettyTextRepresentable協(xié)議的同時,也需要遵循TextRepresentable協(xié)議。

如下所示,用擴(kuò)展SnakesAndLadders遵循PrettyTextRepresentable協(xié)議:

    extension SnakesAndLadders: PrettyTextRepresentable {
        func asPrettyText() -> String {
            var output = asText() + ":\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
        }
    }

for in中迭代出了board數(shù)組中的每一個元素:

  • 當(dāng)從數(shù)組中迭代出的元素的值大于0時,用表示
  • 當(dāng)從數(shù)組中迭代出的元素的值小于0時,用表示
  • 當(dāng)從數(shù)組中迭代出的元素的值等于0時,用表示

任意SankesAndLadders的實例都可以使用asPrettyText()方法。

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

類專屬協(xié)議

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

    protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
        // class-only protocol definition goes here
    }

在以上例子中,協(xié)議SomeClassOnlyProtocol只能被類(class)類型適配。如果嘗試讓結(jié)構(gòu)體或枚舉類型適配該協(xié)議,則會出現(xiàn)編譯錯誤。

注意

當(dāng)協(xié)議需求定義的行為,要求(或假設(shè))它的遵循類型必須是引用語義而非值語義時,應(yīng)該采用類專屬協(xié)議。關(guān)于引用語義,值語義的更多內(nèi)容,請查看結(jié)構(gòu)體和枚舉是值類型類是引用類型

協(xié)議合成

一個協(xié)議可由多個協(xié)議采用protocol<SomeProtocol, AnotherProtocol>這樣的格式進(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(celebrator: protocol<Named, Aged>) {
        println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
    }
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    wishHappyBirthday(birthdayPerson)
    // 輸出 "Happy birthday Malcolm - you're 21!

Named協(xié)議包含String類型的name屬性;Aged協(xié)議包含Int類型的age屬性。Person結(jié)構(gòu)體遵循了這兩個協(xié)議。

wishHappyBirthday函數(shù)的形參celebrator的類型為protocol<Named,Aged>??梢詡魅肴我?code>遵循這兩個協(xié)議的類型的實例

注意: 協(xié)議合成并不會生成一個新協(xié)議類型,而是將多個協(xié)議合成為一個臨時的協(xié)議,超出范圍后立即失效。

檢驗協(xié)議的一致性

使用isas操作符來檢查協(xié)議的一致性或轉(zhuǎn)化協(xié)議類型。檢查和轉(zhuǎn)化的語法和之前相同(詳情查看Typy Casting章節(jié)):

  • is操作符用來檢查實例是否遵循了某個協(xié)議。
  • as?返回一個可選值,當(dāng)實例遵循協(xié)議時,返回該協(xié)議類型;否則返回nil
  • as用以強(qiáng)制向下轉(zhuǎn)型。
    @objc protocol HasArea {
        var area: Double { get }
    }

注意: @objc用來表示協(xié)議是可選的,也可以用來表示暴露給Objective-C的代碼,此外,@objc型協(xié)議只對有效,因此只能在中檢查協(xié)議的一致性。

如下所示,定義了CircleCountry類,它們都遵循了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實現(xiàn)為基于存儲型屬性radius的計算型屬性,Country類則把area實現(xiàn)為存儲型屬性。這兩個類都遵循HasArea協(xié)議。

如下所示,Animal是一個沒有實現(xiàn)HasArea協(xié)議的類

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

Circle,Country,Animal并沒有一個相同的基類,因而采用AnyObject類型的數(shù)組來裝載在他們的實例,如下所示:

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

objects數(shù)組使用字面量初始化,數(shù)組包含一個radius為2。0的Circle的實例,一個保存了英國面積的Country實例和一個legs為4的Animal實例。

如下所示,objects數(shù)組可以被迭代,對迭代出的每一個元素進(jìn)行檢查,看它是否遵循了HasArea協(xié)議:

    for object in objects {
        if let objectWithArea = object as? HasArea {
            println("Area is \(objectWithArea.area)")
        } else {
            println("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ù)龅脑刈裱?code>HasArea協(xié)議時,通過as?操作符將其可選綁定(optional binding)objectWithArea常量上。objectWithAreaHasArea協(xié)議類型的實例,因此area屬性是可以被訪問和打印的。

objects數(shù)組中元素的類型并不會因為向下轉(zhuǎn)型而改變,它們?nèi)匀皇?code>Circle,CountryAnimal類型。然而,當(dāng)它們被賦值給objectWithArea常量時,則只被視為HasArea類型,因此只有area屬性能夠被訪問。

對可選協(xié)議的規(guī)定

可選協(xié)議含有可選成員,其遵循者可以選擇是否實現(xiàn)這些成員。在協(xié)議中使用@optional關(guān)鍵字作為前綴來定義可選成員。

可選協(xié)議在調(diào)用時使用可選鏈,詳細(xì)內(nèi)容在Optional Chaining章節(jié)中查看。

someOptionalMethod?(someArgument)這樣,你可以在可選方法名稱后加上?來檢查該方法是否被實現(xiàn)。可選方法可選屬性都會返回一個可選值(optional value),當(dāng)其不可訪問時,?之后語句不會執(zhí)行,并整體返回nil

注意: 可選協(xié)議只能在含有@objc前綴的協(xié)議中生效。且@objc的協(xié)議只能被遵循

如下所示,Counter類使用含有兩個可選成員的CounterDataSource協(xié)議類型的外部數(shù)據(jù)源來提供增量值(increment amount)

    @objc protocol CounterDataSource {
        optional func incrementForCount(count: Int) -> Int
        optional var fixedIncrement: Int { get }
    }

CounterDataSource含有incrementForCount可選方法fiexdIncrement可選屬性,它們使用了不同的方法來從數(shù)據(jù)源中獲取合適的增量值。

注意: CounterDataSource中的屬性和方法都是可選的,因此可以在類中聲明但不實現(xiàn)這些成員,盡管技術(shù)上允許這樣做,不過最好不要這樣寫。

Counter類含有CounterDataSource?類型的可選屬性dataSource,如下所示:

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

count屬性用于存儲當(dāng)前的值,increment方法用來為count賦值。

increment方法通過可選鏈,嘗試從兩種可選成員中獲取count

  1. 由于dataSource可能為nil,因此在dataSource后邊加上了?標(biāo)記來表明只在dataSource非空時才去調(diào)用incrementForCount方法。

  2. 即使dataSource存在,但是也無法保證其是否實現(xiàn)了incrementForCount方法,因此在incrementForCount方法后邊也加有?標(biāo)記

在調(diào)用incrementForCount方法后,Int可選值通過可選綁定(optional binding)自動拆包并賦值給常量amount。

當(dāng)incrementForCount不能被調(diào)用時,嘗試使用可選屬性fixedIncrement來代替。

ThreeSource實現(xiàn)了CounterDataSource協(xié)議,如下所示:

class ThreeSource: CounterDataSource {
    let fixedIncrement = 3
}

使用ThreeSource作為數(shù)據(jù)源開實例化一個Counter:

    var counter = Counter()
    counter.dataSource = ThreeSource()
    for _ in 1...4 {
        counter.increment()
        println(counter.count)
    }
    // 3
    // 6
    // 9
    // 12

TowardsZeroSource實現(xiàn)了CounterDataSource協(xié)議中的incrementForCount方法,如下所示:

    class TowardsZeroSource: CounterDataSource {
    func incrementForCount(count: Int) -> Int {
            if count == 0 {
                return 0
            } else if count < 0 {
                return 1
            } else {
                return -1
            }
        }
    }

下邊是執(zhí)行的代碼:

    counter.count = -4
    counter.dataSource = TowardsZeroSource()
    for _ in 1...5 {
        counter.increment()
        println(counter.count)
    }
    // -3
    // -2
    // -1
    // 0
    // 0