Agreement (ii)
This article is followed by the previous agreement (i) to summarize.
1. Delegate (proxy) mode
A delegate is a design pattern that allows a class or struct to delegate some of the functionality that is required for them to an instance of another type. The implementation of the delegate pattern is simple: Define the protocol to encapsulate the functions and methods that need to be delegated, so that their followers have these delegated functions and parties
Method. The delegate pattern can be used to respond to a specific action or to receive data from an external data source without needing to know the type information of the external data source.
The following example is a two dice game-based protocol:
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)}
The DICEGAME protocol can be implemented in any game that contains dice. The Dicegamedelegate protocol can be used to track the Dicegame game process.
As shown below, Snakesandladders is a new version of the game in Snakes and Ladders (Control Flow Chapter has a detailed description of the game). The new version uses Dice as the dice and implements the Dicegame and Dicegamedelegate protocols, which are used to record the game's process:
class SnakesAndLadders: DiceGame {let finalSquare = 25let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())var square = 0var board: [Int]init() {board = [Int](count: finalSquare + 1, repeatedValue: 0)board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08}var delegate: DiceGameDelegate?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)}}
This version of the game is encapsulated in the Snakesandladders class, which follows the Dicegame protocol and provides the corresponding readable dice properties and play instance methods. (The Dice property is not changed after construction, and the protocol requires only dice to be read-only, so dice is declared as a constant property.) )
The game initializes the game using the Snakesandladders class's constructor (initializer). All game logic is transferred to the play method in the protocol, and the play method uses the dice attribute provided by the protocol to provide the value of the dice roll out.
Note: Delegate is not a prerequisite for the game, so delegate is defined as an optional attribute that follows the Dicegamedelegate protocol. Because
The delegate is an optional value, so it is automatically assigned nil at initialization time. You can then set the appropriate values for the delegate in the game.
The Dicegamedelegate protocol provides three ways to track the game. is placed in the logic of the game, which is within the play () method. At the beginning of the game, the game is called at the end of a new round.
Because delegate is an optional property that follows Dicegamedelegate, an optional chain is used in the play () method to invoke the delegate method. If the delegate property is nil, the method called by delegate is invalidated and does not produce an error. If delegate is not nil, then the method can be called. As shown below, Dicegametracker follows the Dicegamedelegate protocol
class DiceGameTracker: DiceGameDelegate {var numberOfTurns = 0func gameDidStart(game: DiceGame) {numberOfTurns = 0if 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) {++numberOfTurnsprint("Rolled a \(diceRoll)")}func gameDidEnd(game: DiceGame) {print("The game lasted for \(numberOfTurns) turns")}}
Dicegametracker implements the three methods specified in the Dicegamedelegate protocol to record the number of rounds that the game has already performed. When the game starts, the Numberofturns property is assigned a value of 0; Increment in each new round; After the game is over, output the total number of rounds of the print game.
The Gamedidstart method obtains the game information from the Games parameter and outputs it. The game is treated as a dicegame type instead of a snakeandladders type in the method, so only members in the Dicegame protocol can be accessed in the method. Of course, these methods can also be used after the type conversion
Call. In the previous example code, the IS operator checks if the game is an instance of the Snakesandladders type, and if so, prints the corresponding content.
Regardless of which game is currently being played, game follows the DICEGAME protocol to ensure that the game contains the dice attribute, so the Gamedidstart (_:) The Dice property can be accessed by passing in the game parameter to print out the value of the dice sides property.
The operation of the Dicegametracker is as follows:
let tracker = DiceGameTracker()let game = SnakesAndLadders()game.delegate = trackergame.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
2. Adding a protocol member to an extension
Even if the source code cannot be modified, the existing type (class, struct, enumeration, and so on) can still be extended by extension (Extension). Extensions can add attributes, methods, subscript scripts, and other members to existing types.
Attention:
When you follow a protocol by extending to a type that already exists, all instances of that type are also added to the method in the protocol. For example, the Textrepresentable protocol, any type that wants to represent some textual content can follow that protocol. The content that you want to represent can be a description of the type itself, or it can be a version of the current content:
protocol TextRepresentable {func asText() -> String}
You can extend the Dice class to follow the functionality of the Textrepresentable protocol, as mentioned in the previous section
extension Dice: TextRepresentable {func asText() -> String {return "A \(sides)-sided dice"}}
Now, by extension, the Dice type follows a new protocol, and the Dice type is declared as having the same effect as when it is defined to follow the Textrepresentable protocol. At the time of extension, the protocol name is written after the type name, separated by a colon, and the newly added protocol content is indicated in curly braces.
Now all instances of Dice follow the Textrepresentable protocol:
let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator())print(d12.asText())// 输出 "A 12-sided dice"同样 SnakesAndLadders 类也可以通过 扩展 的方式来遵循 TextRepresentable 协议:extension SnakesAndLadders: TextRepresentable {func asText() -> String {return "A game of Snakes and Ladders with \(finalSquare) squares"}}print(game.asText())// 输出 "A game of Snakes and Ladders with 25 squares"
3. By extending the Supplemental Agreement statement
When a type has implemented all of the requirements in the Protocol and is not declared to adhere to the Protocol, the Protocol Declaration can be supplemented by an extended (empty extension):
struct Hamster {var name: Stringfunc asText() -> String {return "A hamster named \(name)"}}extension Hamster: TextRepresentable {}
From now on, Hamster instances can be used as textrepresentable types
let simonTheHamster = Hamster(name: "Simon")let somethingTextRepresentable: TextRepresentable = simonTheHamsterprint(somethingTextRepresentable.asText())// 输出 "A hamster named Simon"
Attention
Even if all the requirements of the Protocol are met, the type does not automatically change, so you must make an explicit protocol declaration for it.
4. Protocol types in the collection
The protocol type can be used in the collection, which means that the elements in the collection are protocol types, and the following example creates an array of type textrepresentable:
let things: [TextRepresentable] =[game,d12,simonTheHamster]
As shown below, the things array can be traversed directly and the text representation of each element is printed:
for thing in things {print(thing.asText())}// A game of Snakes and Ladders with 25 squares// A 12-sided dice// A hamster named Simon
Thing are treated as textrepresentable types rather than Dice, dicegame, Hamster, etc. So you can and can only invoke the Astext method
5. Inheritance of the agreement
A protocol can inherit one or more other protocols, adding new content requirements based on inherited protocols. The inheritance syntax for a protocol is similar to that of a class, with multiple inherited protocols separated by commas:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {// 协议定义}如下所示, PrettyTextRepresentable 协议继承了 TextRepresentable 协议protocol PrettyTextRepresentable: TextRepresentable {func asPrettyText() -> String}
The example defines a new protocol prettytextrepresentable, which inherits from the Textrepresentable protocol. Any type that complies with the Prettytextrepresentable agreement must also meet the requirements of the Textrepresentable agreement in meeting the requirements of the agreement. In this example, the Prettytextrepresentable protocol requires its followers to provide a Asprettytext method with a return value of type String.
Expand Snakesandladders to follow the Prettytextrepresentable protocol, as shown below:
extension SnakesAndLadders: PrettyTextRepresentable {func asPrettyText() -> String {var output = asText() + ":\n"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}}
The above extensions allow Snakesandladders to follow the Prettytextrepresentable protocol and provide the Asprettytext () method for each Snakesandladders type with protocol requirements. Each prettytextrepresentable type is also a textrepresentable type, so the Astext () method can be called in the implementation of Asprettytext. A newline character is then added to each line as the beginning of the output. It then iterates through the elements in the array, outputting a geometry to represent the result of the traversal:
When the value of an element removed from an array is greater than 0 o'clock, a ▲ is used to indicate
When the value of the element removed from the array is less than 0 o'clock, the representation
When the value of an element taken from an array is equal to 0 o'clock, it is represented by 0
An instance of any sankesandladders can use the Asprettytext () method.
print(game.asPrettyText())// A game of Snakes and Ladders with 25 squares:// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
6. Class-Specific agreements
You can restrict the protocol to the class type by adding the class keyword in the list of inherited protocols. (structs or enumerations cannot follow the protocol.) The class keyword must be the first to appear in the list of inherited protocols, followed by other inheritance protocols.
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {// class-only protocol definition goes here}
In the above example, the protocol Someclassonlyprotocol can only be adapted by the class type. If you attempt to adapt the Protocol to a struct or enumeration type, a compilation error occurs.
Attention
A class-specific protocol should be used when the protocol wants to define the behavior that requires (or assumes) that its compliance type must be reference semantics rather than value semantics.
Protocol compositing sometimes requires multiple protocols to be followed. You can use multiple protocols with protocol
7. Conformance of inspection protocols
You can use the IS and as operators to check whether a protocol is followed or coerced into a certain type.
The IS operator is used to check if an instance follows a protocol
As? Returns an optional value that returns the protocol type when the instance follows the protocol;
As is used to force a downward transition, a run-time error can occur if a strong turn fails.
The following example defines a HASAREA protocol that requires a Double type of area to be readable:
protocol HasArea {var area: Double { get }}
As shown below, the Circle and country classes are defined, and they all follow the Hasarea protocol
class Circle: HasArea {let pi = 3.1415927var radius: Doublevar area: Double { return pi * radius * radius }init(radius: Double) { self.radius = radius }}class Country: HasArea {var area: Doubleinit(area: Double) { self.area = area }}
The Circle class implements the area as a computed property based on the storage-type attribute radius, and the country class implements the area as a storage-type attribute. All two of these classes follow the Hasarea protocol.
As shown below, animal is a class that does not implement the Hasarea protocol
class Animal {var legs: Intinit(legs: Int) { self.legs = legs }}
Circle, country, Animal do not have an identical base class, however, they are classes, and their instances can be stored in the same array as variables of type anyobject:
let objects: [AnyObject] = [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)]
The objects array uses literal initialization, and the array contains an instance of the Circle with radius 2, a country instance that holds the UK area, and a Animal instance with a legs of 4.
As shown below, the objects array can be iterated, checking each iteration of an element to see if it follows the Hasarea protocol:
for object in objects {if let objectWithArea = object as? HasArea {print("Area is \(objectWithArea.area)")} else {print("Something that doesn‘t have an area")}}// Area is 12.5663708// Area is 243610.0// Something that doesn‘t have an area
When an iterative element follows the Hasarea protocol, it is passed as? The optional operator binds its optional binding (the binding) to the Objectwitharea constant. Objectwitharea is an instance of the Hasarea protocol type, so the area property can be accessed and printed.
The types of elements in the objects array do not lose type information because of strong, they are still Circle, country, Animal types. However, when they are assigned to the Objectwitharea constant, they are only treated as hasarea types, so only the area property can be accessed.
8. Provisions on optional agreements
The protocol can contain optional members, and its followers can choose whether to implement those members. Use the optional keyword as a prefix in the protocol to define optional members.
The Optional protocol uses an optional chain when invoked, because the Protocol's followers may not implement the optional content. Like Someoptionalmethod? (someargument) So you can add it after the optional method name? To check whether the method is implemented. Optional methods and optional properties return an optional value (optional value) when it is inaccessible. The statement does not execute and returns nil as a whole.
Attention
An optional protocol can only take effect in an agreement that contains a @objc prefix. and @objc protocol can only be followed by a class that indicates that the protocol will be exposed to the OBJECTIVE-C code, see Using Swift with Cocoa and objective-c for details. Even if you do not intend to interact with objective-c, if you want to indicate that the protocol contains optional attributes, you should add @obj prefix.
The following example defines an integer addition class called Counter, which uses an external data source to achieve each increment. The data source is two optional properties, defined in the Counterdatasource protocol:
@objc protocol CounterDataSource {optional func incrementForCount(count: Int) -> Intoptional var fixedIncrement: Int { get }}
Counterdatasource contains Incrementforcount optional methods and fiexdincrement optional properties that use different methods to get the appropriate increment value from the data source.
Attention
The properties and methods in Counterdatasource are optional, so it is not possible to declare them in a class, although it is technically permissible to do so, but it is best not to do so. Counter class contains Counterdatasource? The optional attribute of type DataSource, as follows:
@objc class Counter {var count = 0var dataSource: CounterDataSource?func increment() {if let amount = dataSource?.incrementForCount?(count) {count += amount} else if let amount = dataSource?.fixedIncrement? {count += amount}}}
Class Counter uses count to store the current value. The class also defines a increment method, which increments the value of count each time the method is called.
The increment () method first attempts to use the Incrementforcount (:) method to get the increment each time. The increment () method uses an optional chain to try to call Incrementforcount (:) and pass in the current count value as a parameter.
Two alternative chain methods are used here. Since DataSource may be nil, it is added behind the DataSource. tag to indicate that the Incrementforcount method is called only when DataSource is not empty. Even if DataSource exists, there is no guarantee that it implements the Incrementforcount method, so it is added behind the Incrementforcount method. Mark.
Calling the Incrementforcount method can fail in either of these cases, so the return value is optional Int type. Although in Counterdatasource, Incrementforcount is defined as a non-optional int (non-optional), we still need to return an optional int type.
After calling the Incrementforcount method, an Int type optional value is automatically disassembled and assigned to the constant amount by an optional binding (optional binding). If the optional value does contain a numeric value, which means both the delegate and the method are present, then the amount is added to count and the operation is completed.
If not from Incrementforcount (_:) Gets the value, either DataSource is nil, or it does not implement the Incrementforcount method-then the increment () method attempts to get the increment from the Fixedincrement property of the data source. Fixedincrement is also a selectable, so after the property name is added? To attempt to retrieve the value of an optional property. As before, the return value is selectable.
Threesource implements the Counterdatasource protocol, which implements the optional attribute fixedincrement, which returns a value of 3 each time:
@objc class ThreeSource: CounterDataSource {let fixedIncrement = 3}
You can use an instance of Threesource as the data source for a Counter instance:
var counter = Counter()counter.dataSource = ThreeSource()for _ in 1...4 {counter.increment()print(counter.count)}// 3// 6// 9// 12
The code above creates a new Counter instance, sets its data source to an Treesource instance, and calls increment () 4 times. As you would expect, the value of count increases by 3 each time it is called.
The following is a more complex data source, Towardszerosource, which will make the final value 0:
class TowardsZeroSource: CounterDataSource {func incrementForCount(count: Int) -> Int {if count == 0 {return 0} else if count < 0 {return 1} else {return -1}}}
Towardszerosource Implements the Incrementforcount (_:) in the Counterdatasource protocol. method, based on the Count parameter, calculates the increment for each. If Count is already 0, the method returns 0, which means there will be no more increments thereafter.
You can use Towardszerosource instances and Counter instances to increase from 4 to 0. Once added to 0, the value will no longer change.
In the example below, it will be increased from 4 to 0. Once the result is 0, it is not increasing:
counter.count = -4counter.dataSource = TowardsZeroSource()for _ in 1...5 {counter.increment()print(counter.count)}// -3// -2// -1// 0// 0
9. Protocol Extensions
The extension protocol can be used to provide an implementation of a method or property for the subject. In this way, you do not have to implement it once in every subject, and you can define it by extending the protocol without using global functions.
For example, you can extend the RandomNumberGenerator protocol to provide the Randombool () method. This method is implemented using the random () method required in the protocol:
extension RandomNumberGenerator {func randomBool() -> Bool {return random() > 0.5}}
By extending the protocol, all the Protocol's followers are automatically given the added method of this extension without any modification.
let generator = LinearCongruentialGenerator()print("Here‘s a random number: \(generator.random())")// 输出 "Here‘s a random number: 0.37464991998171"print("And here‘s a random Boolean: \(generator.randomBool())")// 输出 "And here‘s a random Boolean: true"
9.1 Providing a default implementation
You can provide a default implementation of the properties and methods prescribed by the Protocol by means of Protocol extensions. If the Protocol's followers provide their own implementation of the prescribed properties and methods, then the implementation provided by the following will be used.
Attention
The protocol implementations provided by the extended protocol differ from the Optional Protocol provisions. Although the protocol followers do not have to implement them themselves, the default implementation provided by the extension may not be called with an optional chain.
For example, the Prettytextrepresentable protocol, which inherits the Textrepresentable protocol, can provide a default Asprettytext () method to simplify the return value
Extension Prettytextrepresentable {
Func Asprettytext (), String {
Return Astext ()
}
}
9.2 Adding a throttling condition for a protocol extension
When extending the protocol, you can specify some restrictions that only the protocol followers that satisfy these limits can obtain the properties and methods provided by the protocol extension. These restrictions are written after the protocol name, using the WHERE keyword to describe the throttling situation. :
For example, you can extend the CollectionType protocol, but only for elements that follow textrepresentable:
extension CollectionType where Generator.Element : TextRepresentable {func asList() -> String {return "(" + ", ".join(map({$0.asText()})) + ")"}}
The Aslist () method represents each element in the form of Astext (), and the last comma-delimited link.
Now let's look at Hamster, which follows textrepresentable:
let murrayTheHamster = Hamster(name: "Murray")let morganTheHamster = Hamster(name: "Morgan")let mauriceTheHamster = Hamster(name: "Maurice")let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
Because the array follows the COLLECTIONTYPE protocol, the elements of the arrays follow the Textrepresentable protocol, so the array can use the Aslist () method to get the text representation of the array contents:
print(hamsters.asList())// 输出 "(A hamster named Murray, A hamster named Morgan, A hamster named Maurice)"
Attention
If there are multiple protocol extensions, and the compliance of a protocol satisfies both of them, then the extension that satisfies the most limit will be used.
Summary: The usage of the protocol in Swift is basically that, thank you for reading.
Swift learns the eighth gun--Agreement (II)