美文网首页
Swift-Protocols协议

Swift-Protocols协议

作者: Noah牛YY | 来源:发表于2015-12-19 10:10 被阅读127次

    协议 定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为 遵循(conform) 这个协议。
    除了遵循协议的类型必须实现那些指定的规定以外,还可以对协议进行扩展,实现一些特殊的规定或者一些附加的功能,使得遵循的类型能够收益。

    协议的语法

    协议的定义方式与类,结构体,枚举的定义非常相似。

    protocol SomeProtocol {
      // 协议内容
    }
    

    要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号 : 分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号 , 分隔。

    struct SomeStructure: FirstProtocol, AnotherProtocol {
      // 结构体内容
    }
    

    如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。

    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
      // 类的内容
    }
    

    对属性的规定

    协议可以规定其 遵循者 提供特定名称和类型的 实例属性(instance property) 或 类属性(type property) ,而不用指定是 存储型属性(stored property) 还是 计算型属性(calculate property) 。此外还必须指明是只读的还是可读可写的。
    如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。
    协议中的通常用var来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,只读属性则用 {get } 来表示。

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

    在协议中定义类属性(type property)时,总是使用 static 关键字作为前缀。当协议的遵循者是类时,可以使用class 或 static 关键字来声明类属性:

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

    如下所示,这是一个含有一个实例属性要求的协议:

    protocol FullyNamed {
      var fullName: String { get }
    }
    

    FullyNamed 协议除了要求协议的遵循者提供全名属性外,对协议对遵循者的类型并没有特别的要求。这个协议表示,任何遵循 FullyNamed 协议的类型,都具有一个可读的 String 类型实例属性 fullName 。下面是一个遵循 FullyNamed 协议的简单结构体:

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

    这个例子中定义了一个叫做 Person 的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了 FullyNamed 协议。Person 结构体的每一个实例都有一个 String 类型的存储型属性 fullName 。这正好满足了 FullyNamed 协议的要求,也就意味着, Person 结构体完整的 遵循 了协议。(如果协议要求未被完全满足,在编译时会报错)下面是一个更为复杂的类,它采用并遵循了 FullyNamed 协议:

    class Starship: FullyNamed {
      var prefix: String?
      var name: Stringinit(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 属性实现为只读的计算型属性。每一个 Starship 类的实例都有一个名为 name 的属性和一个名为 prefix 的可选属性。 当 prefix 存在时,将 prefix 插入到 name 之前来为Starship构建 fullName , prefix 不存在时,则将直接用 name 构建 fullName 。

    对方法的规定

    协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。
    正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当协议的遵循者是类的时候,你可以在类的实现中使用 class 或者 static 来实现类方法:

    protocol SomeProtocol {
      static func someTypeMethod()
    }
    

    下面的例子定义了含有一个实例方法的协议:

    protocol RandomNumberGenerator {
      func random() -> Double
    }
    

    RandomNumberGenerator 协议要求其遵循者必须拥有一个名为 random , 返回值类型为 Double 的实例方法。尽管这里并未指明,但是我们假设返回值在[0,1)区间内。
    RandomNumberGenerator 协议并不在意每一个随机数是怎样生成的,它只强调这里有一个随机数生成器。如下所示,下边的是一个遵循了 RandomNumberGenerator 协议的类。该类实现了一个叫做线性同余生成器(linearcongruential 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("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"
    

    委托(代理)模式

    委托是一种设计模式,它允许 类 或 结构体 将一些需要它们负责的功能 交由(或委托) 给其他的类型的实例。委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法,使其 遵循者 拥有这些被委托的 函数和方法 。委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。下面的例子是两个基于骰子游戏的协议:

    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 协议可以在任意含有骰子的游戏中实现。 DiceGameDelegate 协议可以用来追踪 DiceGame 的游戏过程。如下所示, SnakesAndLadders 是 Snakes and Ladders游戏的新版本。新版本使用 Dice 作为骰子,并且实现了 DiceGame 和 DiceGameDelegate 协议,后者用来记录游戏的过程:

    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 协议,并且提供了相应的可读的 dice 属性和 play 实例方法。( dice 属性在构造之后就不再改变,且协议只要求 dice 为只读的,因此将 dice 声明为常量属性。)
    游戏使用 SnakesAndLadders 类的 构造器(initializer) 初始化游戏。所有的游戏逻辑被转移到了协议中的 play 方法, play 方法使用协议规定的 dice 属性提供骰子摇出的值。
    注意: delegate 并不是游戏的必备条件,因此 delegate 被定义为遵循 DiceGameDelegate 协议的可选属性。因为 delegate 是可选值,因此在初始化的时候被自动赋值为 nil 。随后,可以在游戏中为 delegate 设置适当的值。
    DicegameDelegate 协议提供了三个方法用来追踪游戏过程。被放置于游戏的逻辑中,即 play() 方法内。分别在游戏开始时,新一轮开始时,游戏结束时被调用。
    因为 delegate 是一个遵循 DiceGameDelegate 的可选属性,因此在 play() 方法中使用了 可选链 来调用委托方法。 若 delegate 属性为 nil , 则delegate所调用的方法失效,并不会产生错误。若 delegate 不为 nil ,则方法能够被调用
    如下所示, DiceGameTracker 遵循了 DiceGameDelegate 协议:

    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
        print("Rolled a \(diceRoll)")
      }
      func gameDidEnd(game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
      }
    }
    

    DiceGameTracker 实现了 DiceGameDelegate 协议规定的三个方法,用来记录游戏已经进行的轮数。 当游戏开始时, numberOfTurns 属性被赋值为0; 在每新一轮中递增; 游戏结束后,输出打印游戏的总轮数。
    gameDidStart 方法从 game 参数获取游戏信息并输出。 game 在方法中被当做 DiceGame 类型而不是 SnakeAndLadders 类型,所以方法中只能访问 DiceGame 协议中的成员。当然了,这些方法也可以在类型转换之后调用。在上例代码中,通过 is 操作符检查 game 是否为 SnakesAndLadders 类型的实例,如果是,则打印出相应的内容。
    无论当前进行的是何种游戏, game 都遵循 DiceGame 协议以确保 game 含有 dice 属性,因此在 gameDidStart(_:) 方法中可以通过传入的 game 参数来访问 dice 属性,进而打印出 dice 的 sides 属性的值。
    DiceGameTracker 的运行情况,如下所示:

    let tracker = DiceGameTracker()
    let game = SnakesAndLadders()
    game.delegate = trackergame.play()
    // 开始一个新的Snakes and Ladders的游戏
    // 游戏使用 6 面的骰子
    // 翻转得到 3
    // 翻转得到 5
    // 翻转得到 4
    // 翻转得到 5
    // 游戏进行了 4 轮
    

    文章摘自开发者手册

    相关文章

      网友评论

          本文标题:Swift-Protocols协议

          本文链接:https://www.haomeiwen.com/subject/xjnphttx.html