• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Swift5.4语言指南(二十三)协议

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739783.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

协议定义的该适合特定任务或片的功能的方法,属性和其他要求的蓝图。该协议然后可以采用由一个类,结构,或枚举,以提供实际实施方案的这些要求。满足协议要求的任何类型都被称为符合该协议。

除了指定必须符合标准的类型的要求之外,您还可以扩展协议以实现这些要求中的某些要求,或者实施符合标准的类型可以利用的其他功能。

协议语法

您以与类,结构和枚举非常相似的方式定义协议:

  1. protocol SomeProtocol {
  2. // protocol definition goes here
  3. }

自定义类型声明它们采用特定的协议,方法是将协议名称放在类型名称之后,并用冒号分隔,以作为其定义的一部分。可以列出多个协议,并用逗号分隔:

  1. struct SomeStructure: FirstProtocol, AnotherProtocol {
  2. // structure definition goes here
  3. }

如果一个类具有超类,请在其采用的任何协议之前列出超类名称,并以逗号开头:

  1. class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
  2. // class definition goes here
  3. }

物业要求

协议可以要求任何符合条件的类型来提供具有特定名称和类型的实例属性或类型属性。协议没有指定该属性是存储属性还是计算属性,仅指定所需的属性名称和类型。该协议还指定每个属性必须是可获取的还是获取的可设置的。

如果协议要求某个属性必须是可获取和可设置的,则该属性的要求不能由常量存储的属性或只读的计算属性来满足。如果协议仅要求一个属性是可获取的,则该要求可以由任何种类的属性来满足,并且对于可用于您自己的代码的属性,也可以对其进行设置是有效的。

属性要求始终声明为变量属性,并以var关键字为前缀gettable和settable属性在类型声明后通过写来指示,而gettable属性通过write来指示get set }get }

  1. protocol SomeProtocol {
  2. var mustBeSettable: Int { get set }
  3. var doesNotNeedToBeSettable: Int { get }
  4. }

static在协议中定义类型属性要求时,请始终在其前面加上关键字。即使类型属性要求在由类实现时可以以classorstatic关键字作为前缀,该规则也适用

  1. protocol AnotherProtocol {
  2. static var someTypeProperty: Int { get set }
  3. }

这是一个具有单实例属性要求的协议示例:

  1. protocol FullyNamed {
  2. var fullName: String { get }
  3. }

FullyNamed协议需要一个符合标准的类型来提供完全限定的名称。该协议未指定有关符合类型的性质的任何其他信息,仅指定该类型必须能够为其自身提供全名。该协议规定,任何FullyNamed类型必须有称为gettable实例属性fullName,它的类型的String

这是采用并符合FullyNamed协议的简单结构的示例

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

本示例定义了一个名为的结构Person,该结构代表一个特定的命名人。它声明它采用该FullyNamed协议作为其定义的第一行的一部分。

的每个实例Person都有一个名为的存储属性fullName,类型为String这符合FullyNamed协议的单一要求,并且意味着Person已正确符合协议。(如果未满足协议要求,Swift会在编译时报告错误。)

这是一个更复杂的类,它也采用并遵守该FullyNamed协议:

  1. class Starship: FullyNamed {
  2. var prefix: String?
  3. var name: String
  4. init(name: String, prefix: String? = nil) {
  5. self.name = name
  6. self.prefix = prefix
  7. }
  8. var fullName: String {
  9. return (prefix != nil ? prefix! + " " : "") + name
  10. }
  11. }
  12. var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
  13. // ncc1701.fullName is "USS Enterprise"

此类将fullName属性要求实现为飞船的已计算的只读属性。每个Starship类实例存储一个必填name和一个可选的prefixfullName属性使用该prefix值(如果存在),并将其添加到的开头name以为星际飞船创建全名。

方法要求

协议可能要求特定的实例方法和类型方法要通过符合类型来实现。这些方法以与普通实例和类型方法完全相同的方式编写为协议定义的一部分,但没有花括号或方法主体。可变参数是允许的,但要遵循与常规方法相同的规则。但是,不能在协议的定义中为方法参数指定默认值。

与类型属性要求一样,static在协议中定义类型方法要求,请始终在其前面加上关键字。即使类型方法要求在由类实现时classorstatic关键字为前缀也是如此

  1. protocol SomeProtocol {
  2. static func someTypeMethod()
  3. }

以下示例定义了一个具有单实例方法要求的协议:

  1. protocol RandomNumberGenerator {
  2. func random() -> Double
  3. }

该协议RandomNumberGenerator要求任何符合条件的类型都有一个称为的实例方法random,该实例方法在被调用时会返回一个Double值。尽管未将其指定为协议的一部分,但假设此值是从0.0的数字(但不包括)1.0

RandomNumberGenerator协议对如何生成每个随机数没有任何假设,它只是要求生成器提供生成新随机数的标准方法。

这是采用并符合RandomNumberGenerator协议的类的实现此类实现称为线性同余生成器的伪随机数生成器算法

  1. class LinearCongruentialGenerator: RandomNumberGenerator {
  2. var lastRandom = 42.0
  3. let m = 139968.0
  4. let a = 3877.0
  5. let c = 29573.0
  6. func random() -> Double {
  7. lastRandom = ((lastRandom * a + c)
  8. .truncatingRemainder(dividingBy:m))
  9. return lastRandom / m
  10. }
  11. }
  12. let generator = LinearCongruentialGenerator()
  13. print("Here's a random number: \(generator.random())")
  14. // Prints "Here's a random number: 0.3746499199817101"
  15. print("And another one: \(generator.random())")
  16. // Prints "And another one: 0.729023776863283"

变异方法要求

有时有必要使用一种方法来修改(或变异)它所属的实例。对于值类型(即结构和枚举)的实例方法,请将mutating关键字放在方法的func关键字之前,以指示允许该方法修改其所属的实例以及该实例的任何属性。在实例方法修改值类型中介绍了此过程

如果您定义了一个协议实例方法要求,该要求旨在使采用该协议的任何类型的实例发生变异,请将该方法标记为mutating关键字,作为协议定义的一部分。这使结构和枚举可以采用该协议并满足该方法要求。

笔记

如果将协议实例方法的要求标记为mutating,则mutating在为类编写该方法的实现时不需要编写关键字。mutating关键字仅由结构和枚举。

下面的示例定义了一个名为的协议Togglable,该协议定义了一个名为的单实例方法要求toggle顾名思义,该toggle()方法旨在切换或反转任何符合类型的状态,通常是通过修改该类型的属性来实现。

协议定义中,toggle()mutating关键字标记方法Togglable,以表明该方法在被调用时将使符合实例的状态发生变化:

  1. protocol Togglable {
  2. mutating func toggle()
  3. }

如果您Togglable为某个结构或枚举实现协议,则该结构或枚举可以通过提供该toggle()方法的实现(也标记为)来符合该协议mutating

以下示例定义了一个名为的枚举OnOffSwitch此枚举在两个状态之间切换,由枚举用on和表示off枚举的toggle实现标记为mutating,以符合Togglable协议的要求:

  1. enum OnOffSwitch: Togglable {
  2. case off, on
  3. mutating func toggle() {
  4. switch self {
  5. case .off:
  6. self = .on
  7. case .on:
  8. self = .off
  9. }
  10. }
  11. }
  12. var lightSwitch = OnOffSwitch.off
  13. lightSwitch.toggle()
  14. // lightSwitch is now equal to .on

初始化程序要求

协议可能要求特定的初始化程序通过一致的类型来实现。您可以使用与普通初始化程序完全相同的方式将这些初始化程序编写为协议定义的一部分,但不使用花括号或初始化程序主体:

  1. protocol SomeProtocol {
  2. init(someParameter: Int)
  3. }

协议初始化程序要求的类实现

您可以在符合条件的类上实现协议初始化程序要求,既可以将其指定为初始化程序,也可以作为便捷初始化程序。在这两种情况下,都必须使用required修饰符标记初始化程序的实现

  1. class SomeClass: SomeProtocol {
  2. required init(someParameter: Int) {
  3. // initializer implementation goes here
  4. }
  5. }

使用required修饰符可确保您在符合类的所有子类上提供初始化程序要求的显式或继承实现,以使它们也符合协议。

有关必需的初始化程序的更多信息,请参见必需的初始化程序

笔记

您不需要在用required修饰符标记的类上修饰符标记协议初始化程序实现final,因为最终类不能被子类化。有关final修饰符的更多信息,请参见防止覆盖

如果子类覆盖超类中的指定初始化程序,并且还通过协议实现了匹配的初始化程序要求,请同时使用requiredoverride修饰符标记初始化程序的实现

  1. protocol SomeProtocol {
  2. init()
  3. }
  4. class SomeSuperClass {
  5. init() {
  6. // initializer implementation goes here
  7. }
  8. }
  9. class SomeSubClass: SomeSuperClass, SomeProtocol {
  10. // "required" from SomeProtocol conformance; "override" from SomeSuperClass
  11. required override init() {
  12. // initializer implementation goes here
  13. }
  14. }

初始化器失败要求

协议可以为一致性类型定义失败的初始化器要求,如Failable Initializers中所定义

合格的初始化器要求可以由合格或不合格的初始化器类型满足。不可失败的初始化器或隐式展开的可失败初始化器可以满足不可失败的初始化器要求。

协议作为类型

协议本身实际上并没有实现任何功能。但是,您可以将协议用作代码中的完整类型。将协议用作类型有时有时称为存在类型,它来自短语“存在类型T,使得T符合协议”。

您可以在允许使用其他类型的许多地方使用协议,包括:

  • 作为函数,方法或初始化程序中的参数类型或返回类型
  • 作为常量,变量或属性的类型
  • 作为数组,字典或其他容器中项目的类型

笔记

由于协议的类型,开始他们的名称以大写字母(如FullyNamedRandomNumberGenerator),以配合其他类型的雨燕的名称(如IntStringDouble)。

这是用作类型的协议的示例:

  1. class Dice {
  2. let sides: Int
  3. let generator: RandomNumberGenerator
  4. init(sides: Int, generator: RandomNumberGenerator) {
  5. self.sides = sides
  6. self.generator = generator
  7. }
  8. func roll() -> Int {
  9. return Int(generator.random() * Double(sides)) + 1
  10. }
  11. }

本示例定义了一个名为的新类Dice该类表示在棋盘游戏中使用n面骰子。Dice实例具有一个名为的整数属性sides,该属性表示它们有多少边;以及一个名为的属性generator,该属性提供了一个随机数生成器,可以从中生成骰子掷骰值。

generator属性是类型RandomNumberGenerator因此,您可以将其设置为采用该协议任何类型的实例RandomNumberGenerator分配给该属性的实例不需要任何其他操作,只是该实例必须采用该RandomNumberGenerator协议。因为其类型为RandomNumberGenerator,所以Dice内的代码只能以generator适用于所有符合此协议的生成器的方式进行交互这意味着它不能使用由生成器的基础类型定义的任何方法或属性。但是,您可以按照从超类向下转换为子类的相同方式,从协议类型向下转换为基础类型,如Downcasting中所述

Dice还有一个初始化程序,用于设置其初始状态。这个初始化有一个名为参数generator,这也是类型RandomNumberGenerator初始化新Dice实例时,可以将任何符合类型的值传递给此参数

Dice提供一个实例方法,roll方法返回1到骰子边数之间的整数值。此方法调用生成器的random()方法在0.0之间创建一个新的随机数1.0,并使用该随机数在正确范围内创建骰子掷骰值。因为generator已知采用RandomNumberGenerator,所以保证有一个random()调用方法。

Dice是使用类创建LinearCongruentialGenerator随机实例生成器的六面骰子的方法

  1. var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
  2. for _ in 1...5 {
  3. print("Random dice roll is \(d6.roll())")
  4. }
  5. // Random dice roll is 3
  6. // Random dice roll is 5
  7. // Random dice roll is 4
  8. // Random dice roll is 5
  9. // Random dice roll is 4

代表团

委托是一种设计模式,使类或结构可以其某些职责移交给(或委托)另一种类型的实例。通过定义封装委托职责的协议来实现此设计模式,从而确保符合类型(称为委托)可以提供已委托的功能。委托可用于响应特定操作,或从外部源检索数据而无需了解该源的基础类型。

下面的示例定义了两种用于基于骰子的棋盘游戏的协议:

  1. protocol DiceGame {
  2. var dice: Dice { get }
  3. func play()
  4. }
  5. protocol DiceGameDelegate: AnyObject {
  6. func gameDidStart(_ game: DiceGame)
  7. func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
  8. func gameDidEnd(_ game: DiceGame)
  9. }

DiceGame协议是可以被涉及骰子的任何游戏采用的协议。

DiceGameDelegate协议可以用来跟踪进度DiceGame为防止强引用循环,将委托声明为弱引用。有关弱引用的信息,请参见类实例之间的强引用循环将协议标记为仅类,可以使SnakesAndLadders本章稍后类声明其委托必须使用弱引用。只有A类的协议是通过从它的继承标记AnyObject,如在讨论类只有协议

这是最初在Control Flow中引入Snakes and Ladders游戏的一个版本该版本适用于其骰子实例;采纳该协议;并通知其进度:DiceDiceGameDiceGameDelegate

  1. class SnakesAndLadders: DiceGame {
  2. let finalSquare = 25
  3. let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
  4. var square = 0
  5. var board: [Int]
  6. init() {
  7. board = Array(repeating: 0, count: finalSquare + 1)
  8. board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
  9. board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
  10. }
  11. weak var delegate: DiceGameDelegate?
  12. func play() {
  13. square = 0
  14. delegate?.gameDidStart(self)
  15. gameLoop: while square != finalSquare {
  16. let diceRoll = dice.roll()
  17. delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
  18. switch square + diceRoll {
  19. case finalSquare:
  20. break gameLoop
  21. case let newSquare where newSquare > finalSquare:
  22. continue gameLoop
  23. default:
  24. square += diceRoll
  25. square += board[square]
  26. }
  27. }
  28. delegate?.gameDidEnd(self)
  29. }
  30. }

有关“蛇和 梯@@子 ”游戏玩法的描述,请参见Break

此版本的游戏包装为名为的类SnakesAndLadders该类采用了DiceGame协议。它提供了一个gettabledice属性和一种play()方法以符合协议。(将该dice属性声明为常量属性,因为初始化后不需要更改属性,并且协议仅要求该属性必须是可获取的。)

蛇和 梯@@子 游戏板的设置采取类的内进行init()初始化。所有游戏逻辑都移到了协议的play方法中,该方法使用协议的requireddice属性提供骰子掷骰值。

请注意,该delegate属性被定义为optional DiceGameDelegate,因为玩游戏不需要委托。由于该delegate属性是可选类型,因此该属性会自动设置为的初始值nil此后,游戏实例化程序可以选择将属性设置为合适的委托人。因为该DiceGameDelegate协议是仅类的,所以您可以将委托声明为,weak以防止引用循环。

DiceGameDelegate提供了三种跟踪游戏进度的方法。这三种方法已被并入上述play()方法的游戏逻辑中,并在新游戏开始,新回合开始或游戏结束时被调用。

因为该delegate属性是可选的 DiceGameDelegate,所以该play()方法每次在委托上调用方法时都使用可选的链接。如果该delegate属性为nil,则这些委托调用将正常失败并且没有错误。如果delegate属性为非nil,则调用委托方法,并将该SnakesAndLadders实例作为参数传递给该方法

下一个示例显示了一个名为的类DiceGameTracker该类采用了以下DiceGameDelegate协议:

  1. class DiceGameTracker: DiceGameDelegate {
  2. var numberOfTurns = 0
  3. func gameDidStart(_ game: DiceGame) {
  4. numberOfTurns = 0
  5. if game is SnakesAndLadders {
  6. print("Started a new game of Snakes and Ladders")
  7. }
  8. print("The game is using a \(game.dice.sides)-sided dice")
  9. }
  10. func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
  11. numberOfTurns += 1
  12. print("Rolled a \(diceRoll)")
  13. }
  14. func gameDidEnd(_ game: DiceGame) {
  15. print("The game lasted for \(numberOfTurns) turns")
  16. }
  17. }

DiceGameTracker实现所需的所有三种方法DiceGameDelegate它使用这些方法来跟踪游戏进行的回合数。numberOfTurns当游戏开始时,它将属性重置为零,每次新回合开始时将其递增,并在游戏结束后打印出总回合数。

gameDidStart(_:)上面显示的实现使用该game参数来打印有关将要玩的游戏的一些介绍性信息。game参数具有类型的DiceGame,而不是SnakesAndLaddersgameDidStart(_:)可以访问和使用只方法和被实现为的部件属性DiceGame的协议。但是,该方法仍然可以使用类型转换来查询基础实例的类型。在此示例中,它检查game实际上是否SnakesAndLadders幕后实例,如果,则打印适当的消息。

gameDidStart(_:)方法还访问dice传递的game参数属性由于game已知符合该DiceGame协议,因此可以保证具有dice属性,因此该gameDidStart(_:)方法能够访问和打印骰子的sides属性,而不管正在玩哪种游戏。

这是实际的DiceGameTracker外观:

  1. let tracker = DiceGameTracker()
  2. let game = SnakesAndLadders()
  3. game.delegate = tracker
  4. game.play()
  5. // Started a new game of Snakes and Ladders
  6. // The game is using a 6-sided dice
  7. // Rolled a 3
  8. // Rolled a 5
  9. // Rolled a 4
  10. // Rolled a 5
  11. // The game lasted for 4 turns

通过扩展添加协议一致性

即使您无权访问现有类型的源代码,也可以扩展现有类型以采用并遵循新协议。扩展可以向现有类型添加新的属性,方法和下标,因此可以添加协议可能要求的任何要求。有关扩展的更多信息,请参见扩展

笔记

当在扩展中将该一致性添加到实例的类型时,该类型的现有实例会自动采用并符合协议。

例如,此协议称为TextRepresentable,可以通过任何一种可以表示为文本的方式来实现。这可能是对自身的描述,也可能是其当前状态的文本版本:

  1. protocol TextRepresentable {
  2. var textualDescription: String { get }
  3. }

Dice上面类可以扩展为采用和遵循TextRepresentable

  1. extension Dice: TextRepresentable {
  2. var textualDescription: String {
  3. return "A \(sides)-sided dice"
  4. }
  5. }

此扩展采用新协议的方式与Dice原始实现中提供的方式完全相同协议名称在类型名称之后提供,并用冒号分隔,并在扩展的花括号内提供协议所有要求的实现。

Dice现在可以将任何实例视为TextRepresentable

  1. let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
  2. print(d12.textualDescription)
  3. // Prints "A 12-sided dice"

同样,SnakesAndLadders可以将游戏类扩展为采用并符合TextRepresentable协议:

  1. extension SnakesAndLadders: TextRepresentable {
  2. var textualDescription: String {
  3. return "A game of Snakes and Ladders with \(finalSquare) squares"
  4. }
  5. }
  6. print(game.textualDescription)
  7. // Prints "A game of Snakes and Ladders with 25 squares"

有条件地遵守协议

通用类型仅在某些条件下(例如,当该类型的通用参数符合该协议时)才能够满足协议的要求。您可以通过在扩展类型时列出约束来使泛型类型有条件地符合协议。通过编写一个通用where子句,在要采用的协议名称之后编写这些约束有关泛型where子句的更多信息,请参见泛型子句

以下扩展使Array实例TextRepresentable在它们存储符合的类型的元素时就符合协议TextRepresentable

  1. extension Array: TextRepresentable where Element: TextRepresentable {
  2. var textualDescription: String {
  3. let itemsAsText = self.map { $0.textualDescription }
  4. return "[" + itemsAsText.joined(separator: ", ") + "]"
  5. }
  6. }
  7. let myDice = [d6, d12]
  8. print(myDice.textualDescription)
  9. // Prints "[A 6-sided dice, A 12-sided dice]"

声明协议采用扩展

如果类型已经符合协议的所有要求,但尚未声明采用该协议,则可以使它采用带有空扩展名的协议:

  1. struct Hamster {
  2. var name: String
  3. var textualDescription: String {
  4. return "A hamster named \(name)"
  5. }
  6. }
  7. extension Hamster: TextRepresentable {}

Hamster现在可以TextRepresentable在所需类型的任何地方使用的实例

  1. let simonTheHamster = Hamster(name: "Simon")
  2. let somethingTextRepresentable: TextRepresentable = simonTheHamster
  3. print(somethingTextRepresentable.textualDescription)
  4. // Prints "A hamster named Simon"

笔记

类型不会仅通过满足协议的要求就自动采用协议。他们必须始终明确声明其对协议的采用。

通过综合实现采用协议

斯威夫特可以自动提供协议一致性的EquatableHashable以及Comparable在很多简单的情况。使用这种综合的实现意味着您不必编写重复的样板代码即可自己实现协议要求。

Swift提供了Equatable以下几种自定义类型的综合实现

  • 仅存储符合Equatable协议属性的结构
  • 仅具有符合Equatable协议的关联类型的枚举
  • 没有关联类型的枚举

要接收的综合实现==,请Equatable在包含原始声明的文件中声明对的符合性,而无需==自己实现运算符。Equatable协议提供的默认实现!=

以下示例Vector3D为三维位置矢量定义了一种结构,类似于该结构。因为性能都是一个的类型,接收合成的等价运营商的实现。(x, y, z)Vector2DxyzEquatableVector3D

  1. struct Vector3D: Equatable {
  2. var x = 0.0, y = 0.0, z = 0.0
  3. }
  4. let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
  5. let

    鲜花

    握手

    雷人

    路过

    鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Swift学习笔记(语法篇)--数据类型发布时间:2022-07-13
下一篇:
Swift实战-QQ在线音乐(AppleWatch版)发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap