The Swift Programming Language学习笔记(二十三)——协议

原创 2016年02月25日 17:27:59

协议

协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。

除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。

协议语法

使用protocol定义协议。协议的定义方式与类、结构体和枚举的定义非常相似。要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。采纳多个协议时,各协议之间用逗号(,)分隔。拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔。

class A {

}

protocol B {

}

protocol C {

}

struct  D: B, C {

}

class E: A, B, C {

}

属性要求

协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是只读的还是可读可写的

如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的

协议通常用var关键字来声明变量属性,在类型声明后加上 { set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

在协议中定义类型属性时,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字来声明类型属性。

protocol A {
    var a: Int { get set }
    var b: Int { get }  // 如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的。
}

protocol B {
    static var a: Int { get set }
}

/**
 * FullyNamed协议除了要求采纳协议的类型提供fullName属性外,并没有其他特别的要求。这个协议表示,任何采纳FullyNamed的类型,都必须有一个只读的String类型的实例属性fullName。
 */
protocol FullyNamed {
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String    // Person结构体的每一个实例都有一个String类型的存储型属性fullName。这正好满足了FullyNamed协议的要求,也就意味着Person结构体正确地符合了协议。
}

let p = Person(fullName: "Tim")

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }

    var fullName: String {  // Starship类把fullName属性实现为只读的计算型属性。
        return (prefix != nil ? prefix! + " " : "") + name
    }
}

let s = Starship(name: "Enterprise", prefix: "USS")
print(s.fullName)

方法要求

协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值

正如属性要求中所述,在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字作为前缀。

protocol A {
    static func a()
}

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
 * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
 */
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()
print(generator.random())
print(generator.random())
print(generator.random())
/*
0.37464991998171
0.729023776863283
0.636466906721536
*/

在上面的代码中,实现了“线性同余生成器(linear congruential generator)”的伪随机数算法

mutating方法要求

有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值

如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加mutating关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。

注意,实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须写mutating关键字。

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case On, Off
    mutating func toggle() {    // 当使用枚举或结构体来实现Togglable协议时,需要提供一个带有mutating前缀的toggle() 方法。
        switch self {
        case .On:
            self = .Off
        case .Off:
            self = .On
        }
    }
}

var o = OnOffSwitch.On
o.toggle()
print(o)

构造器要求

协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体。

构造器要求在类中的实现

你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上required修饰符。使用required修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。

如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注requiredoverride修饰符。

protocol A {
    init()
}

class B {
    init() {
        print("B.init() called")
    }
}

class C: B, A {
    required override init() {
        print("C.init() called")
    }
}

可失败构造器要求

协议还可以为采纳协议的类型定义可失败构造器要求。采纳协议的类型可以通过可失败构造器(init?)或非可失败构造器(init来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!来满足。

协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。

协议可以像其他普通类型一样使用,使用场景如下:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

注意,协议是一种类型,因此协议类型的名称应与其他类型(例如IntDoubleString)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamedRandomNumberGenerator)。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

/**
 * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。
 */
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
    }
}

let d = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 0..<10 {
    print(d.roll())
}
/*
3
5
4
5
4
1
4
2
1
4
*/

委托(代理)模式

委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

/**
 * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。
 */
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
    }
}

// 两个基于骰子游戏的协议

/**
 * DiceGame协议可以被任意涉及骰子的游戏采纳。
 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/**
 * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。
 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/**
 * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。
 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    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?     // delegate并不是游戏的必备条件,设置为可选
    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)
    }
}

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(game: DiceGame) {     // game参数是DiceGame类型而不是SnakeAndLadders类型,所以在方法中只能访问DiceGame协议中的内容。
        numberOfTurns = 0
        if game is SnakeAndLadders {
            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
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}

let game = SnakeAndLadders()
let tracker = DiceGameTracker()
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
*/

通过扩展添加协议一致性

即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。

通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。

通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

/**
 * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。
 */
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
    }
}

// 两个基于骰子游戏的协议

/**
 * DiceGame协议可以被任意涉及骰子的游戏采纳。
 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/**
 * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。
 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/**
 * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。
 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    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?     // delegate并不是游戏的必备条件,设置为可选
    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)
    }
}

/**
 * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述
 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/**
 * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议
 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

let d = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d.textualDescription)     // A 12-sided dice

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

let s = SnakeAndLadders()
print(s.textualDescription)     // A game of Snakes and Ladders with 25 squares

通过扩展采纳协议

当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议

即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

// 现在Hamster实例可以作为TextRepresentable类型使用了!
let h = Hamster(name: "Tim")
let t: TextRepresentable = h
print(t.textualDescription)     // A hamster named Tim

协议类型的集合

协议类型可以在数组或者字典这样的集合中使用。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

/**
 * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。
 */
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
    }
}

// 两个基于骰子游戏的协议

/**
 * DiceGame协议可以被任意涉及骰子的游戏采纳。
 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/**
 * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。
 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/**
 * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。
 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    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?     // delegate并不是游戏的必备条件,设置为可选
    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)
    }
}

/**
 * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述
 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/**
 * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议
 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

let s = SnakeAndLadders()
let d = Dice(sides: 6, generator: LinearCongruentialGenerator())
let h = Hamster(name: "Tim")
let ts: [TextRepresentable] = [s, h, d]     // 必须显式声明类型!?否则报出现歧义的错误
for item in ts {
    print(item.textualDescription)  // 任何TextRepresentable的实例都有一个textualDescription属性,所以在每次循环中可以安全地访问textualDescription属性
}
/*
A game of Snakes and Ladders with 25 squares
A hamster named Tim
A 6-sided dice
*/

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

/**
 * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。
 */
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
    }
}

// 两个基于骰子游戏的协议

/**
 * DiceGame协议可以被任意涉及骰子的游戏采纳。
 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/**
 * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。
 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/**
 * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。
 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    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?     // delegate并不是游戏的必备条件,设置为可选
    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)
    }
}

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

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

extension SnakeAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = self.textualDescription + ":\n"
        for i in 1...finalSquare {
            switch board[i] {
            case let ladder where ladder > 0:
                output += "▲、"
            case let ladder where ladder < 0:
                output += "▼、"
            default:
                output += "○、"
            }
        }
        return output
    }
}

let s = SnakeAndLadders()
print(s.prettyTextualDescription)
/*
A game of Snakes and Ladders with 25 squares:
○、○、▲、○、○、▲、○、○、▲、▲、○、○、○、▼、○、○、○、○、▼、○、○、▼、○、▼、○、
*/

类类型专属协议

可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前。

当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。

protocol A {

}

protocol B: class, A {

}

协议合成

有时候需要同时采纳多个协议,你可以将多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为协议合成(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>) {     // 不关心参数的具体类型,只要参数符合这两个协议即可
    print("Happy birthday \(celebrator.name) -- you are \(celebrator.age)")
}

let p = Person(name: "TIm", age: 23)
wishHappyBirthday(p)    // Happy birthday TIm -- you are 23

检查协议一致性

可以使用类型转换中描述的isas操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查转换到某个协议类型在语法上和类型的检查和转换完全相同:

  • is用来检查实例是否符合某个协议,若符合则返回true,否则返回false
  • as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误
protocol HasArea {
    var area: Double { get }
}

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return radius * radius }     // 计算型属性
    init(radius: Double) { self.radius = radius }
}

class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }         // 存储型属性
}

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

let objects: [AnyObject] = [
    Circle(radius: 3.2),
    Country(area: 123456.0),
    Animal(legs: 4)
]

for o in objects {
    if let oo = o as? HasArea {
        print(oo.area)
    } else {
        print("area没有值")
    }
}

可选的协议要求

协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为(Int) -> String的方法会变成((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来调用可选方法。

注意,可选的协议要求只能用在标记@objc特性的协议中。该特性表示协议将暴露给Objective-C代码,详情参见Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和Objective-C有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上@obj特性。

还需要注意的是,标记@objc特性的协议只能被继承自Objective-C类的类或者@objc类采纳,其他类以及结构体和枚举均不能采纳这种协议。

import Foundation   // @objc需要导入Foundation框架
@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int  // 可选的函数类型:(Int -> Int)?
    optional var fixedIncrement: Int { get }            // 可选的Int:Int?
}

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

class ThreeSource: NSObject, CounterDataSource {
    let  fixedIncrement = 3
}

let counter = Counter()
counter.dataSource = ThreeSource()
for _ in 0..<3 {
    counter.increment()
    print(counter.count)
}
/*
3
6
9
*/

print("")

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

let a = Counter()
a.count = -4
a.dataSource = TowardsZeroSource()
for _ in 0..<5 {
    a.increment()
    print(a.count)
}
/*
-3
-2
-1
0
0
*/

严格来讲,CounterDataSource协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。

协议扩展

协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。

protocol RandomNumberGenerator {
    func random() -> Double
}

/**
* 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法
*/
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
    }
}

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



let l = LinearCongruentialGenerator()
print(l.random())       // 0.37464991998171
print(l.randomBool())   // true,即使扩展是在LinearCongruentialGenerator定义之后也可以正常调用

提供默认实现

可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。

注意,通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。

protocol TextRepresentable {
    var textualDescription: String { get }
}

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

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription   // 默认的prettyTextualDescription属性,只是简单地返回textualDescription属性的值
    }
}

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述。

如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

/**
 * 扩展CollectionType协议,但是只适用于集合中的元素采纳了TextRepresentable协议的情况
 */
extension CollectionType where Generator.Element: TextRepresentable {
    var textualDescription: String {
        let itemAsText = self.map { $0.textualDescription }
        return "[" + itemAsText.joinWithSeparator(", ") + "]"
    }
}

let tim = Hamster(name: "Tim")
let kate = Hamster(name: "Kate")
let jack = Hamster(name: "Jack")
let hs = [tim, kate, jack]
print(hs.textualDescription)    // [A hamster named Tim, A hamster named Kate, A hamster named Jack],Array符合CollectionType协议,而数组中的元素又符合TextRepresentable协议
版权声明:本文为博主原创文章,未经博主允许不得转载。

The Swift Programming Language 中英文双语版

目录 欢迎使用 Swift 关于 SwiftSwift 初见 Swift 教程 基础部分基本运算符字符串和字符集合类型控制流函数闭包枚举类和结构体属性方法附属脚本继承构造过程析构过程自...
  • sunnyboy9
  • sunnyboy9
  • 2016年03月10日 23:50
  • 584

The Swift Programming Language 3.0版本的更新

Swift更新至3.0版本. 更新了函数和函数声明章节中关于函数的讨论, 新版本中所有的参数都默认获得一个标签. 指定Attribute参数时,新版本中使用”:”替代之前的”=”. 在Switch分支...
  • showgp
  • showgp
  • 2016年06月16日 10:27
  • 2084

《The C Programming Language》电子书下载

至于《The C Programming Language》这本书多么好,多么权威,我就不多说了,看过的人都知道。。。。  点击此处下载《The C Programming Language》 【...
  • pengqianhe
  • pengqianhe
  • 2012年09月25日 16:25
  • 5843

The Swift Programming Language中文/英文版

github:https://github.com/numbbbbb/the-swift-programming-language-in-chinese The Swift Programmin...
  • zhuming3834
  • zhuming3834
  • 2016年01月16日 14:38
  • 746

The Swift Programming Language学习笔记

The Swift Programming Language学习笔记
  • qq_34131328
  • qq_34131328
  • 2017年12月01日 13:28
  • 32

《The C++ Programming Language》作者的经典语录

        早上读了《The C++ Programming Language》作者的经典语录,渐渐发觉这曾经是自己走过的路。额头上逐渐冒汗,为自己曾经的不踏实和单纯幼稚深深自责……把它贴在这里,...
  • ericksky
  • ericksky
  • 2007年06月09日 10:14
  • 3219

《The C Programming Language》读书笔记总结 <一>.基础篇

写了这么多年的C代码,回过头来再看《The C Programming Language》这本书,作者Brian W. Kernighan和C语言之父Dennis M. Ritchie。感觉里面的知识...
  • Eastmount
  • Eastmount
  • 2015年10月21日 16:14
  • 2250

《The Objective-C Programming Language》中文翻译文档

July 8,2014
  • holyjoy
  • holyjoy
  • 2014年07月09日 16:47
  • 1620

中文版 Apple 官方 Swift 教程《The Swift Programming Language》

Swift 初见 本页内容包括: 简单值(Simple Values)控制流(Control Flow)函数和闭包(Functions and Closures)对象和类(Objects ...
  • investzhu
  • investzhu
  • 2014年06月13日 19:37
  • 1697

<<The C Programming Language>>学习之路-练习题参考答案 1-1

从今天开始系统将C圣经书>练习题全部做一遍,书已经看几遍了,自己感觉看多少遍也不如自己亲手做一遍,开始这段学习旅程吧,即使今年剩余的空余时间都做了这练习,也相信编程能力会有较大的飞跃吧。 由于目前家...
  • kuwaka
  • kuwaka
  • 2014年03月26日 23:37
  • 1016
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:The Swift Programming Language学习笔记(二十三)——协议
举报原因:
原因补充:

(最多只允许输入30个字)