第二十一章 协议

6. Protocols as Types (协议当作类型)

协议实际上并不能自行实现任何功能,尽管如此我们可以把协议当作普通类型那样在我们的代码中使用。把协议当作类型使用有些时候我们称之为 “存在类型” (existential type),来自于 “ 如果存在某个类型T,那么这个类型T就会遵循某个协议 ”。Using a protocol as a type is sometimes called an existential type, which comes from the phrase “there exists a type T such that T conforms to the protocol”.

我们可以在很多地方使用到某个类型允许的协议,包括了:

  • 作为函数,方法或者构造器的参数类型或返回类型。
    As a parameter type or return type in a function, method, or initializer
  • 作为常量,变量和属性的一个类型
    As the type of a constant, variable, or property
  • 作为数组,字典或其他容器的一个类型
    As the type of items in an array, dictionary, or other container

下面是一个协议当作类型使用的例子,该例定义了一个新的类Dice,表示在游戏中使用的一个n面的骰子。该类的实例有一个属性generator,用来生成一个用来摇骰子的随机的点数,属性generator是一个RandomNumberGenerator(协议)的类型,所以可以给属性可以设置任何类型中的任何一个实例,实例也会采用和遵循个此协议,所以我们分配给这个属性的也没有其他对该实例的要求了,除了该实例必须遵循这个协议,因为它是一个类型是RandomNumberGenerator这个协议。Dice类中的代码只能以适用于所有符合此协议的生成器的方式与生成器进行交互。这意味着它无法使用由生成器的基础类型定义的任何方法或属性,但是,我们可以把它像父类到子类的方式向下转换成一个基础类型。

class Dice {let sides: Intlet generator: RandomNumberGeneratorinit(sides: Int, generator: RandomNumberGenerator) {self.sides = sidesself.generator = generator}func roll() -> Int {return Int(generator.random() * Double(sides)) + 1}
}

Dice同样提供了一个构造器,用来设置骰子的初始状态,该构造器有一个generator的参数, 是一个RandomNumberGenerator的类型,所以我们可以传入任何确定类型的值给这个参数,当Dice实例初始化的时候。

Dice提供了一个实例方法roll,返回的整数类型值正好是骰子所在面的点数,实例方法roll会调用生成器的random()方法,来创建一个0.01.0之间的随机数字,用生成的随机数字来确定摇骰子的数字在一个合理的范围内。因为generator它是遵循这个协议RandomNumberGenerator的 所以说就能一定能调用random()方法。

下面是这个类Dice是如何用实例LinearCongruentialGenerator来作为一个随机数字生成器创建一个6面的骰子的。

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. Delegation (代理)

代理是类和结构体交出(代理)某些职责给其他类型的实例的一个设计模式,定义封装这种职责的协议可以实现这种设计模式。代理可以用来响应一个特定的行为或从内部代码中检索接受数据,并不需要知道与之代码所相关的基础类型,Delegation can be used to respond to particular action, or to retrieve data from an external source without needing to know the underlying type of that source.

下面这个例子定义了两个协议用以骰子游戏中

protocol DiceGame {var dice: Dice { get }func play()
}
// class-only protocol with AnyObject 参考相关章节
protocol DiceGameDelegate: AnyObject {func gameDidStart(_ game: DiceGame)func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)func gameDidEnd(_ game: DiceGame)
}

这个协议DiceGameDelegate协议被用来采用和追踪这个DiceGame,从来预防强引用循环,所以这个代理被定义为弱引用,更多有关弱引用的内容详见Strong Reference Cycles Between Class Instances ,所以将这个协议定义为类类型协议(class-only protocol),可以使代理的类SnalesAndLadders同样采用这个弱应用,类类型的协议将会在后面章节中有详细描述。

下面这个版本的蛇和梯子的游戏的基础描述和玩法在控制流中有介绍过,实例Dice在这个版本中用于摇骰子的这个任务,从而来采用这个DiceGame协议,来通知DiceGameDelegate整个游戏运作的过程。

class SnakesAndLadders: DiceGame {// 蛇和梯子游戏棋盘let finalSquare = 25// 和骰子的设定let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())var square = 0// 棋盘上面的规则点数变化设定var board: [Int]init() {board = Array(repeating: 0, count: finalSquare + 1)board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08}// 参考强弱引用相关篇章 weak var delegate: DiceGameDelegate?// 方法play() 实现了游戏的逻辑func play() {square = 0delegate?.gameDidStart(self)gameLoop: while square != finalSquare {let diceRoll = dice.roll()delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)switch square + diceRoll {case finalSquare:break gameLoopcase let newSquare where newSquare > finalSquare:continue gameLoopdefault:square += diceRollsquare += board[square]}}delegate?.gameDidEnd(self)}
}

这个版本的游戏封装到了SnakesAndLadders类中,该类遵循了DiceGame协议,并且提供了相应的可读的dice属性和play()方法。(dice属性在构造之后就不再改变,且协议只要求 dice 为可读的,因此将dice声明为常量属性. )

游戏使用SnakesAndLadders类的init()构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的play()方法,play()方法使用协议要求的dice属性提供骰子摇出的值。注意delegate并不是游戏的必备条件,因此delegate被定义为DiceGameDelegate类型的可选属性。因为delegate是可选值,因此会被自动赋予初始值nil。随后可以在游戏中为delegate设置适当的值。

DicegameDelegate协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中play()方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。因为 delegate是一个DiceGameDelegate类型的可选属性,因此在play()方法中通过可选链式调用来调用它的方法。若delegate属性为nil,则调用方法会失败,并不会产生错误。若delegate不为nil,则方法能够被调用,并传递SnakesAndLadders实例作为参数。

下面这个例子是类DiceGameTracker同样也采用了协议DiceGameDelegate

class DiceGameTracker: DiceGameDelegate {var numberOfTurns = 0// 1. start 游戏开始的时候轮数为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")}// 2. re-start 再次开始新一轮游戏,轮数加1func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {numberOfTurns += 1print("Rolled a \(diceRoll)")}// 3. end 游戏结束 并输出最后游戏进行的轮数次数。func gameDidEnd(_ game: DiceGame) {print("The game lasted for \(numberOfTurns) turns")}
}

DiceGameDelegate要求DiceGameTracker实现三个方法,用来追踪游戏进行的轮数,所以这个类里面就有一个初始值为0的变量属性numberOfTurns,当游戏开始,再次开始新一轮游戏,和游戏结束并输出最后的轮数。

gameDidStart()方法是利用游戏的参数来输出一些游戏即将开始是的介绍性的信息,这个游戏参数game其实是一个DiceGame的类型。所以说这个方法gameDidStart()只能读取,使用实现在DiceGame协议中方法和属性。其实在幕后方法依然可以使用类型转换来查询蛇和梯子游戏中潜在实例的类型,在上面例子中用来查询game是否是SnakesAndLadders的一个实例

并且在游戏中尽可能地输出相关的信息。gameDidStart()方法也可以用来读取dice属性中被传入的game参数,因为game是遵循这个DiceGame协议,所以game确定会有dice这个属性,所以该方法可以用来读取和输出dice中的sides属性,

实践中的DiceGameTrackers

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

iOS Swift No.21 - 协议2相关推荐

  1. ios swift 实现饼状图进度条,swift环形进度条

    ios swift 实现饼状图进度条 // // ProgressControl.swift // L02MyProgressControl // // Created by plter on 7/2 ...

  2. iOS开发者程序许可协议

    请仔细阅读下面的许可协议条款和条件之前下载或使用苹果软件.   这些条款和条件构成你和苹果之间的法律协议. iOS开发者程序许可协议 目的 你想使用苹果软件(如下定义)来开发一个或多个应用程序(如下定 ...

  3. IOS Swift 入门学习汇总 (更新中..)

    IOS Swift 学习入门 配置区 info 配置 本地化中文 文件导入Xcode CocoaPads 依赖管理工具 UI区 + 代码 通用 打开新页面方式 设置新开页面全屏展示 跳转页面 正向传值 ...

  4. iOS Swift 5中的键盘处理

    This article was originally published at CometChat. 本文最初在CometChat上发布. "Handling Keyboard in iO ...

  5. ios swift请求框架_使用Swift在iOS中创建二进制框架

    ios swift请求框架 We all use a lot of frameworks in our daily development routine. We just type the magi ...

  6. IOS swift项目的单例模式.swift5以后的单例模式

    IOS swift项目的单例模式.swift5以后的单例模式 不能继承NSObject 第一种写法,最简单 class SoundTools{static let sharedInstance = S ...

  7. IOS Socket 01-网络协议基础知识

    IOS Socket 01-网络协议基础知识 1. 网络参考模型 OSI参考模型                                          TCP/IP参考模型 2. 七层简述 ...

  8. iOS Swift 使用 CLLocationManager 定位

    iOS Swift 使用 CLLocationManager 定位 CLLocationManager 是IOS 系统提供的定位对象,通过该对象可以获取定位信息,包括:经纬度.海拔.方向.速度.通过反 ...

  9. ios swift 纯代码设置UITableViewCell的style

    1.dequeueReusableCell(withIdentifier:)方法不需要注册 func tableView(_ tableView: UITableView, cellForRowAt ...

最新文章

  1. solr7.6 安装配置
  2. java重写的特性解释
  3. 【sql进阶】查询每天、每个设备的第一条数据
  4. linux判断字符串命令行,bash – 将命令行参数与字符串进行比较
  5. python课程预告_Python3编程预告
  6. vue如何设置视频封面_vue怎么制作朋友圈封面视频 vue制作朋友圈视频方法
  7. C语言三剑客:C陷阱与缺陷、C和指针、C专家编程
  8. JavaScript设计模式----装饰者模式
  9. oracle 判断是否复数,第 14 章 使用复数运算库
  10. ios标准时间转为北京时间
  11. android 盒子 红白机 模拟器,安卓FC模拟器
  12. pycharm运行scrapy框架爬取豆瓣电影250可能遇到的问题
  13. 轻音乐-Bandari(班得瑞)
  14. 010 面向对象编程
  15. SpringMVC里的Model、Map、ModelMap以及ModelAndView
  16. 【esp8266】③esp8266对接天猫精灵实现语音控制
  17. 认证疑难问题分析报告
  18. 记录一次扩ubuntu的文件系统的过程
  19. tiny core linux网络连接,用Tiny Core Linux打造纯Firefox上网系统(概要)
  20. 计算机数控系统cnc分类,数控机床系统有几种 数控系统的类型和分类

热门文章

  1. Enumerating Trillion Triangles on Distributed Systems
  2. Vue组件通讯的多种方式(个人记录)
  3. 基于Appfuse的Web应用快速开发
  4. Xilinx 7系列FPGA之Virtex-7产品简介
  5. Assertion断言入门(四)——断言覆盖率
  6. 大型智慧灌区信息化管理系统云平台 智慧灌区信息化管理系统解决方案
  7. linux 中文帮助文档
  8. 武汉新时标文化传媒有限公司喜欢看短视频而不是文章?
  9. springcloud五大神兽之Gateway
  10. 【自用】Vue项目中使用自定义字体样式