★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ➤微信公众号:山青咏芝(shanqingyongzhi) ➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/) ➤GitHub地址:https://github.com/strengthen/LeetCode ➤原文地址:https://www.cnblogs.com/strengthen/p/9739783.html ➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。 ➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创! ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
热烈欢迎,请直接点击!!!
进入博主App Store主页,下载使用各个作品!!!
注:博主将坚持每月上线一个新app!!!
甲协议定义的该适合特定任务或片的功能的方法,属性和其他要求的蓝图。该协议然后可以采用由一个类,结构,或枚举,以提供实际实施方案的这些要求。满足协议要求的任何类型都被称为符合该协议。
除了指定必须符合标准的类型的要求之外,您还可以扩展协议以实现这些要求中的某些要求,或者实施符合标准的类型可以利用的其他功能。
协议语法
您以与类,结构和枚举非常相似的方式定义协议:
- protocol SomeProtocol {
- // protocol definition goes here
- }
自定义类型声明它们采用特定的协议,方法是将协议名称放在类型名称之后,并用冒号分隔,以作为其定义的一部分。可以列出多个协议,并用逗号分隔:
- struct SomeStructure: FirstProtocol, AnotherProtocol {
- // structure definition goes here
- }
如果一个类具有超类,请在其采用的任何协议之前列出超类名称,并以逗号开头:
- class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
- // class definition goes here
- }
物业要求
协议可以要求任何符合条件的类型来提供具有特定名称和类型的实例属性或类型属性。协议没有指定该属性是存储属性还是计算属性,仅指定所需的属性名称和类型。该协议还指定每个属性必须是可获取的还是可获取的和可设置的。
如果协议要求某个属性必须是可获取和可设置的,则该属性的要求不能由常量存储的属性或只读的计算属性来满足。如果协议仅要求一个属性是可获取的,则该要求可以由任何种类的属性来满足,并且对于可用于您自己的代码的属性,也可以对其进行设置是有效的。
属性要求始终声明为变量属性,并以var 关键字为前缀。gettable和settable属性在类型声明后通过写来指示,而gettable属性通过write来指示。{ get set } { get }
- protocol SomeProtocol {
- var mustBeSettable: Int { get set }
- var doesNotNeedToBeSettable: Int { get }
- }
static 在协议中定义类型属性要求时,请始终在其前面加上关键字。即使类型属性要求在由类实现时可以以class orstatic 关键字作为前缀,该规则也适用:
- protocol AnotherProtocol {
- static var someTypeProperty: Int { get set }
- }
这是一个具有单实例属性要求的协议示例:
- protocol FullyNamed {
- var fullName: String { get }
- }
该FullyNamed 协议需要一个符合标准的类型来提供完全限定的名称。该协议未指定有关符合类型的性质的任何其他信息,仅指定该类型必须能够为其自身提供全名。该协议规定,任何FullyNamed 类型必须有称为gettable实例属性fullName ,它的类型的String 。
这是采用并符合FullyNamed 协议的简单结构的示例:
- struct Person: FullyNamed {
- var fullName: String
- }
- let john = Person(fullName: "John Appleseed")
- // john.fullName is "John Appleseed"
本示例定义了一个名为的结构Person ,该结构代表一个特定的命名人。它声明它采用该FullyNamed 协议作为其定义的第一行的一部分。
的每个实例Person 都有一个名为的存储属性fullName ,类型为String 。这符合FullyNamed 协议的单一要求,并且意味着Person 已正确符合协议。(如果未满足协议要求,Swift会在编译时报告错误。)
这是一个更复杂的类,它也采用并遵守该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"
此类将fullName 属性要求实现为飞船的已计算的只读属性。每个Starship 类实例存储一个必填name 和一个可选的prefix 。该fullName 属性使用该prefix 值(如果存在),并将其添加到的开头name 以为星际飞船创建全名。
方法要求
协议可能要求特定的实例方法和类型方法要通过符合类型来实现。这些方法以与普通实例和类型方法完全相同的方式编写为协议定义的一部分,但没有花括号或方法主体。可变参数是允许的,但要遵循与常规方法相同的规则。但是,不能在协议的定义中为方法参数指定默认值。
与类型属性要求一样,static 在协议中定义类型方法要求时,请始终在其前面加上关键字。即使类型方法要求在由类实现时以class orstatic 关键字为前缀也是如此:
- protocol SomeProtocol {
- static func someTypeMethod()
- }
以下示例定义了一个具有单实例方法要求的协议:
- protocol RandomNumberGenerator {
- func random() -> Double
- }
该协议RandomNumberGenerator 要求任何符合条件的类型都有一个称为的实例方法random ,该实例方法在被调用时会返回一个Double 值。尽管未将其指定为协议的一部分,但假设此值是从0.0 到的数字(但不包括)1.0 。
该RandomNumberGenerator 协议对如何生成每个随机数没有任何假设,它只是要求生成器提供生成新随机数的标准方法。
这是采用并符合RandomNumberGenerator 协议的类的实现。此类实现称为线性同余生成器的伪随机数生成器算法:
- 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)
- .truncatingRemainder(dividingBy:m))
- return lastRandom / m
- }
- }
- let generator = LinearCongruentialGenerator()
- print("Here's a random number: \(generator.random())")
- // Prints "Here's a random number: 0.3746499199817101"
- print("And another one: \(generator.random())")
- // Prints "And another one: 0.729023776863283"
变异方法要求
有时有必要使用一种方法来修改(或变异)它所属的实例。对于值类型(即结构和枚举)的实例方法,请将mutating 关键字放在方法的func 关键字之前,以指示允许该方法修改其所属的实例以及该实例的任何属性。在实例方法中修改值类型中介绍了此过程。
如果您定义了一个协议实例方法要求,该要求旨在使采用该协议的任何类型的实例发生变异,请将该方法标记为mutating 关键字,作为协议定义的一部分。这使结构和枚举可以采用该协议并满足该方法要求。
笔记
如果将协议实例方法的要求标记为mutating ,则mutating 在为类编写该方法的实现时不需要编写关键字。该mutating 关键字仅由结构和枚举。
下面的示例定义了一个名为的协议Togglable ,该协议定义了一个名为的单实例方法要求toggle 。顾名思义,该toggle() 方法旨在切换或反转任何符合类型的状态,通常是通过修改该类型的属性来实现。
在协议定义中,toggle() 用mutating 关键字标记该方法Togglable ,以表明该方法在被调用时将使符合实例的状态发生变化:
- protocol Togglable {
- mutating func toggle()
- }
如果您Togglable 为某个结构或枚举实现协议,则该结构或枚举可以通过提供该toggle() 方法的实现(也标记为)来符合该协议mutating 。
以下示例定义了一个名为的枚举OnOffSwitch 。此枚举在两个状态之间切换,由枚举用on 和表示off 。枚举的toggle 实现标记为mutating ,以符合Togglable 协议的要求:
- 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
初始化程序要求
协议可能要求特定的初始化程序通过一致的类型来实现。您可以使用与普通初始化程序完全相同的方式将这些初始化程序编写为协议定义的一部分,但不使用花括号或初始化程序主体:
- protocol SomeProtocol {
- init(someParameter: Int)
- }
协议初始化程序要求的类实现
您可以在符合条件的类上实现协议初始化程序要求,既可以将其指定为初始化程序,也可以作为便捷初始化程序。在这两种情况下,都必须使用required 修饰符标记初始化程序的实现:
- class SomeClass: SomeProtocol {
- required init(someParameter: Int) {
- // initializer implementation goes here
- }
- }
使用required 修饰符可确保您在符合类的所有子类上提供初始化程序要求的显式或继承实现,以使它们也符合协议。
有关必需的初始化程序的更多信息,请参见必需的初始化程序。
笔记
您不需要在用required 修饰符标记的类上用修饰符标记协议初始化程序实现final ,因为最终类不能被子类化。有关final 修饰符的更多信息,请参见防止覆盖。
如果子类覆盖超类中的指定初始化程序,并且还通过协议实现了匹配的初始化程序要求,请同时使用required 和override 修饰符标记初始化程序的实现:
- 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
- }
- }
初始化器失败要求
协议可以为一致性类型定义失败的初始化器要求,如Failable Initializers中所定义。
合格的初始化器要求可以由合格或不合格的初始化器类型满足。不可失败的初始化器或隐式展开的可失败初始化器可以满足不可失败的初始化器要求。
协议作为类型
协议本身实际上并没有实现任何功能。但是,您可以将协议用作代码中的完整类型。将协议用作类型有时有时称为存在类型,它来自短语“存在类型T,使得T符合协议”。
您可以在允许使用其他类型的许多地方使用协议,包括:
- 作为函数,方法或初始化程序中的参数类型或返回类型
- 作为常量,变量或属性的类型
- 作为数组,字典或其他容器中项目的类型
笔记
由于协议的类型,开始他们的名称以大写字母(如FullyNamed 和RandomNumberGenerator ),以配合其他类型的雨燕的名称(如Int ,String 和Double )。
这是用作类型的协议的示例:
- 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
- }
- }
本示例定义了一个名为的新类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 随机实例生成器的六面骰子的方法:
- 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
代表团
委托是一种设计模式,使类或结构可以将其某些职责移交给(或委托)另一种类型的实例。通过定义封装委托职责的协议来实现此设计模式,从而确保符合类型(称为委托)可以提供已委托的功能。委托可用于响应特定操作,或从外部源检索数据而无需了解该源的基础类型。
下面的示例定义了两种用于基于骰子的棋盘游戏的协议:
- protocol DiceGame {
- var dice: Dice { get }
- func play()
- }
- protocol DiceGameDelegate: AnyObject {
- func gameDidStart(_ game: DiceGame)
- func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
- func gameDidEnd(_ game: DiceGame)
- }
该DiceGame 协议是可以被涉及骰子的任何游戏采用的协议。
该DiceGameDelegate 协议可以用来跟踪进度DiceGame 。为防止强引用循环,将委托声明为弱引用。有关弱引用的信息,请参见类实例之间的强引用循环。将协议标记为仅类,可以使SnakesAndLadders 本章稍后的类声明其委托必须使用弱引用。只有A类的协议是通过从它的继承标记AnyObject ,如在讨论类只有协议。
这是最初在Control Flow中引入的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 = Array(repeating: 0, count: finalSquare + 1)
- board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
- board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
- }
- weak 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)
- }
- }
有关“蛇和 梯@@子 ”游戏玩法的描述,请参见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 协议:
- 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 += 1
- print("Rolled a \(diceRoll)")
- }
- func gameDidEnd(_ game: DiceGame) {
- print("The game lasted for \(numberOfTurns) turns")
- }
- }
DiceGameTracker 实现所需的所有三种方法DiceGameDelegate 。它使用这些方法来跟踪游戏进行的回合数。numberOfTurns 当游戏开始时,它将属性重置为零,每次新回合开始时将其递增,并在游戏结束后打印出总回合数。
gameDidStart(_:) 上面显示的实现使用该game 参数来打印有关将要玩的游戏的一些介绍性信息。该game 参数具有类型的DiceGame ,而不是SnakesAndLadders 等gameDidStart(_:) 可以访问和使用只方法和被实现为的部件属性DiceGame 的协议。但是,该方法仍然可以使用类型转换来查询基础实例的类型。在此示例中,它检查game 实际上是否是SnakesAndLadders 幕后实例,如果是,则打印适当的消息。
该gameDidStart(_:) 方法还访问dice 传递的game 参数的属性。由于game 已知符合该DiceGame 协议,因此可以保证具有dice 属性,因此该gameDidStart(_:) 方法能够访问和打印骰子的sides 属性,而不管正在玩哪种游戏。
这是实际的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
通过扩展添加协议一致性
即使您无权访问现有类型的源代码,也可以扩展现有类型以采用并遵循新协议。扩展可以向现有类型添加新的属性,方法和下标,因此可以添加协议可能要求的任何要求。有关扩展的更多信息,请参见扩展。
笔记
当在扩展中将该一致性添加到实例的类型时,该类型的现有实例会自动采用并符合协议。
例如,此协议称为TextRepresentable ,可以通过任何一种可以表示为文本的方式来实现。这可能是对自身的描述,也可能是其当前状态的文本版本:
- protocol TextRepresentable {
- var textualDescription: String { get }
- }
Dice 上面的类可以扩展为采用和遵循TextRepresentable :
- extension Dice: TextRepresentable {
- var textualDescription: String {
- return "A \(sides)-sided dice"
- }
- }
此扩展采用新协议的方式与Dice 原始实现中提供的方式完全相同。协议名称在类型名称之后提供,并用冒号分隔,并在扩展的花括号内提供协议所有要求的实现。
Dice 现在可以将任何实例视为TextRepresentable :
- let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
- print(d12.textualDescription)
- // Prints "A 12-sided dice"
同样,SnakesAndLadders 可以将游戏类扩展为采用并符合TextRepresentable 协议:
- extension SnakesAndLadders: TextRepresentable {
- var textualDescription: String {
- return "A game of Snakes and Ladders with \(finalSquare) squares"
- }
- }
- print(game.textualDescription)
- // Prints "A game of Snakes and Ladders with 25 squares"
有条件地遵守协议
通用类型仅在某些条件下(例如,当该类型的通用参数符合该协议时)才能够满足协议的要求。您可以通过在扩展类型时列出约束来使泛型类型有条件地符合协议。通过编写一个通用where 子句,在要采用的协议名称之后编写这些约束。有关泛型where 子句的更多信息,请参见泛型子句。
以下扩展使Array 实例TextRepresentable 在它们存储符合的类型的元素时就符合协议TextRepresentable 。
- extension Array: TextRepresentable where Element: TextRepresentable {
- var textualDescription: String {
- let itemsAsText = self.map { $0.textualDescription }
- return "[" + itemsAsText.joined(separator: ", ") + "]"
- }
- }
- let myDice = [d6, d12]
- print(myDice.textualDescription)
- // Prints "[A 6-sided dice, A 12-sided dice]"
声明协议采用扩展
如果类型已经符合协议的所有要求,但尚未声明采用该协议,则可以使它采用带有空扩展名的协议:
- struct Hamster {
- var name: String
- var textualDescription: String {
- return "A hamster named \(name)"
- }
- }
- extension Hamster: TextRepresentable {}
Hamster 现在可以TextRepresentable 在所需类型的任何地方使用的实例:
- let simonTheHamster = Hamster(name: "Simon")
- let somethingTextRepresentable: TextRepresentable = simonTheHamster
- print(somethingTextRepresentable.textualDescription)
- // Prints "A hamster named Simon"
笔记
类型不会仅通过满足协议的要求就自动采用协议。他们必须始终明确声明其对协议的采用。
通过综合实现采用协议
斯威夫特可以自动提供协议一致性的Equatable ,Hashable 以及Comparable 在很多简单的情况。使用这种综合的实现意味着您不必编写重复的样板代码即可自己实现协议要求。
Swift提供了Equatable 以下几种自定义类型的综合实现:
-
仅存储符合
Equatable 协议属性的结构
-
仅具有符合
Equatable 协议的关联类型的枚举
- 没有关联类型的枚举
要接收的综合实现== ,请Equatable 在包含原始声明的文件中声明对的符合性,而无需== 自己实现运算符。该Equatable 协议提供的默认实现!= 。
以下示例Vector3D 为三维位置矢量定义了一种结构,类似于该结构。因为,和性能都是一个的类型,接收合成的等价运营商的实现。(x, y, z) Vector2D x y z Equatable Vector3D
- struct Vector3D: Equatable {
- var x = 0.0, y = 0.0, z = 0.0
- }
-
- let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
- let
|
请发表评论