二十、Swift 协议 Protocols

1. 概述

协议只提供方法的声明,不提供实现。协议可以被类、结构体和枚举实现。

 

2. 协议的语法 Protocol Syntax

定义一个协议:

    protocol SomeProtocol {
      // protocol definition goes here
    }

如果一个类有父类并且需要遵守某个协议,将父类和协议写在冒号后面:

    class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
      // class definition goes here
    }

 

3. 属性的要求 Property Requirements

如果协议的属性需要实现get/set方法,可以如下实现:

    protocol SomeProtocol {
      var mustBeSettable: Int { get set } // 既要实现set,又要实现get
      var doesNotNeedToBeSettable: Int { get }  // 只要求实现get
    }

使用 class 关键字表明属性从属于类属性,使用 static 关键字表明属性从属于结构体属性和枚举属性:

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

下面定义了一个只有一个属性的协议:

    protocol FullyNamed {
      var fullName: String { get }
    }

定义Person结构体实现上面的协议:

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

因为Person中定义了一个String类型的Stored属性 fullName,也就是Person结构体完全符合了(conformed)FullyNamed协议。

下面定义了一个类,同样实现了(或者说符合、一致)FullyNamed协议:

    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"

 

4. 方法的要求 Method Requirements

协议中方法的参数不能有默认值。

在协议中,用 class 表示类方法,用 static 表示结构体方法和枚举方法(静态方法)。

    protocol SomeProtocol {
      class func someTypeMethod()
    }

比如,在协议中定义一个实例方法:

    protocol RandomNumberGenerator {
      func random() -> Double
    }

LinearCongruentialGenerator 实现了上面的协议:

    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())")
    // prints "Here's a random number: 0.37464991998171"
    println("And another one: \(generator.random())")
    // prints "And another one: 0.729023776863283"

 

5. 可变方法的要求 Mutating Method Requirements

十、方法 Methods我们知道,在值类型(结构体和枚举)中,如果方法想要修改自己的实例,需要定义为 mutating 类型。

如果某个协议中的方法,需要修改实现了这个协议的实例的值,应该将方法定义为mutating 类型。那么,实现了这个协议的枚举和结构体可以修改自己的实例本身。

注意:在类中,不需要写mutating关键字,mutating关键字为值类型专用。

定义一个协议:

    protocol Togglable {
      mutating func toggle()
    }

值类型OnOffSwitch 需要实现这个协议:

    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

 

6. 构造器的需求 Initializer Requirements

 协议中可以定义构造器:

    protocol SomeProtocol {
      init(someParameter: Int)
    }

某个类实现了指定了构造器的协议时,不管是Designated构造器还是Convinence构造器,都需要使用 required 关键字表示:

    class SomeClass: SomeProtocol {
      required init(someParameter: Int) {
        // initializer implementation goes here
      }
    }

required 关键字确保了所有实现了协议的类/子类都要显示/隐式的实现构造器。具体参见十三、初始化 Initialization

如果子类覆盖了父类的Designated构造器,而且需要实现一个协议中的required构造器,那么同时写上override和required关键字:

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

 

7. Failable构造器的需求 Failable Initializer Requirements

协议中可以定义failable initializer requirements,详见十三、初始化 Initialization

协议中定义了 failable initializer requirements,那么实现了协议的类型中对应的构造器可以使failable 也可以是 非failable的。

原话:A failable initializer requirement can be satisfied by a failable or nonfailable initializer on a conforming type. A nonfailable initializer requirement can be satisfied by a nonfailable initializer or an implicitly unwrapped failable initializer.

 

8. 协议作为一种类型 Protocols as Types

协议本身不实现任何功能,协议表明了一些代码将要完成的功能。

因为协议是一种类型,所以你可以在其他类型可以使用的地方使用协议:

  • 1)作为函数、方法、构造器的参数类型和返回值类型。
  • 2)作为常量constant、变量variable、或属性property的类型
  • 3)作为数组、字典和其他容器类型的Items的类型

注意:因为协议是一种类型,所以和其他类型一样(比如Int, String, Double等)使用大写字母开头。

下面的例子将协议作为一种类型来使用:

    protocol RandomNumberGenerator {
      func random() -> Double
    }
    class Dice {
      let sides: Int  // sides面的骰子
      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协议的实例赋值给它,因为确保了它存在generator.random()方法。

创建一个6面的骰子:

    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
      }
    }
    var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
    for _ in 1...5 {
      println("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

 

9. 代理 Delegation

代理是一种设计模式,它允许类和结构体将自己的一些任务交给(代理给)其他类型的实例完成。

下面的例子是在Control Flow.一节的Snakes and Ladders(蛇梯棋)。下面的版本采用一个Dice实例掷筛子,实现DiceGame协议DiceGameDelegate 协议:

image: ../Art/snakesAndLadders_2x.png

    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)
    }
    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 的delegate属性为可选值类型,所以一旦delegate为空时,delegate方法的调用会优雅的结束,而不是报错。如果delegate不为空,那么代理的方法会被调用,并且传入 SnakesAndLadders 实例作为参数。

下面定义了DiceGameTracker

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

gameDidStart函数在游戏开始时设置numberOfTurns为0,它的参数类型为DiceGame而不是SnakesAndLadders ,所以它能访问DiceGame的内容。

gameDidStart通过传入的game参数访问dice属性,因为game是协议类型,所以只要传入实现了这个协议的实例就可以了,而不用管是什么游戏。

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

 

10. 使用扩展增加协议  Adding Protocol Conformance with an Extension

可以使用扩展,给已经实现了某些协议的类型增加新的协议。即使你无法访问这个类型的源代码,也可以给它添加协议。比如下面的例子:

    protocol TextRepresentable {
      func asText() -> String
    }

之前定义的Dice类可以增加上面的协议:

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

现在可以使用asText方法了:

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

同样,也可以给SnakesAndLadders扩展TextRepresentable 协议:

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

如果某类型已经实现了协议中的方法,那么只需要使用extention进行声明就可以了:

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

那么:

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

 

11. 协议的集合 Collections of Protocol Types

协议像数组和字典一样,可以作为一种类型存储在集合中:

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

遍历协议数组:

    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的类型为TextRepresentable,而不是Dice, DiceGame, Hamster,即使他们的实例的实际类型是Dice, DiceGame, Hamster

因为thing的类型是TextRepresentable,定义了asText方法,所以调用thing.asText()是安全的。

 

12. 协议的继承

协议可以继承一个或多个其他协议以给协议增加新的需求,协议的继承与类类似,但是需要列出继承来的多个协议,通过逗号隔开。

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
      // protocol definition goes here
    }

下面定义了一个继承了协议TextRepresentable 的协议:

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

协议PrettyTextRepresentable继承了协议TextRepresentable ,任何实现了协议PrettyTextRepresentable的类型既要满足协议TextRepresentable的要求,也要满足协议PrettyTextRepresentable的要求。

那么:

    extension SnakesAndLadders: PrettyTextRepresentable {
      func asPrettyText() -> String {
        var output = asText() + ":\n" // 调用了子协议中的asText方法
        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
      }
    }

 

那么:

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

 

13. Class-Only 协议

你可以通过 class 关键字,限制一个协议只能被类类型实现,class关键字必须紧跟在冒号后:

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

 

14. 协议的组合 Protocol Composition

使用 protocol<SomeProtocol, AnotherProtocol> 形式将多个协议组合起来,成为一个单一的协议组合体(a single protocol composition requirement)。

比如:

    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) // prints "Happy birthday Malcolm - you're 21!"

上面的例子定义了一个函数 wishHappyBirthday,它只有一个参数celebrator,这个参数为协议组合体,这个参数的类型并不重要,它只是了Named和Aged两个协议的组合体。

注意:协议组合体并不是创建了一个新的协议类型,它只是创建了一个临时的组合了所有协议的组合体。

 

15. 检查协议的实现 Checking for Protocol Conformance

你可以使用十七、类型绑定 Type Casting介绍的 is 关键字检查协议的实现,as 关键字将协议绑定到其他类型,语法与十七章相同。

  • 1)如果一个实例实现了协议,is 表达式返回 true,否则返回 false
  • 2)向下绑定操作符 as? 返回一个协议类型的可选值,如果实例没有实现协议,那么返回 nil
  • 3)向下绑定操作符 as 强制向下绑定到一个协议类型,如果绑定失败,会触发运行时错误。

例如:

    @objc protocol HasArea {
      var area: Double { get }
    }

注意:

  • 1)只有当协议被定义为 @objc 属性时,才能检查它是否被实现。在Using Swift with Cocoa and Objective-C.中介绍了 @objc 关键字用来表明某个协议会被用于Objective-C代码。即使你的代码不与Objective-C进行交互,如果你想检查协议是否被实现,就应该把它定义为 @objc 属性。
  • 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。

下面两个类都实现了HasArea协议:

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

下面一个类没有实现 HasArea 协议:

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

Circle, CountryAnimal没有共同的基类,但是他们都是类类型,所以他们的实例类型都可以用 AnyObject 表示:

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

检查数组元素是否实现了 HasArea 协议:

    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

无论他们是否实现了HasArea 协议, as? 操作符都是返回可选值类型,这个可选值绑定为 objectWithArea 。注意类型绑定并没有改变数据类型,详见类型绑定一节。

 

16. 可选类型协议的要求 Optional Protocol Requirements

你可以给协议定义可选类型的需求(optional requirements),实现协议的类型可以不实现这个需求,使用 optional 关键字表示。

optional protocol requirement 可以被可选值链条调用。可选值链条参见十六、Optional Chaining。

在optional protocol requirement后写一个问号 ? ,比如someOptionalMethod?(someArgument)检查需求是否被实现。当 Optional property requirements 和 optional method requirements 被访问的时候,将永远返回一个合适的可选值,以表明可能没有被实现。

注意:

  • 1)Optional protocol requirements 只能被定义为 @objc 属性。即使你不语Objective-C 代码进行交互,也要写上 @objc ,以表明你想指定 optional requirements。
  • 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。

下面的例子定义了一个使用外部数据源进行数量增长的协

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

下面定义了一个类,它有一个 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
        }
      }
    }

increment方法使用可选值链条尝试去调用incrementForCount方法。

注意这里有两层可选值链条:

第一层中,dataSource可能为 nil ,所以dataSource后有个问号表明只有当dataSourse不为nil时才调用 incrementForCount。

第二层中,即使 dataSource 存在,也不能保证它实现了 incrementForCount 方法,因为这个方法是 optional requirement ,所以 incrementForCount 方法后还写了个问号。

因为 incrementForCount 方法有上面两个可能调用失败的原因,所以不管它的返回值是不是定义为可选值类型,它一定返回可选值类型。

下面定义了数据源类:

    class ThreeSource: CounterDataSource {
      let fixedIncrement = 3
    }

将ThreeSourse作为一个新的Counter实例的数据源:

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

定义一个更复杂的数据源:

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

使用数据源:

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

 

参考:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267

http://c.biancheng.net/cpp/html/2433.html

posted @ 2015-02-04 11:27  action爱生活  阅读(283)  评论(0编辑  收藏  举报