Swift Learning Summary: Protocols

Swift Learning Summary: Protocols
文章图片

Protocols A protocol defines a blueprint of methods, properties.
Can be Adopted and implemented by:

  • class
  • structure
  • enumeration
Syntax
Definition
protocol SomeProtocol { func doSomething() }

Being Conformed
  • By Struct
    struct SomeStruct: FirstProtocol, AnotherProtocol {}

  • By Class
    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {}

Conforming Protocol
The Dog and People both conform the DoSport protocol, so they have responsibility to implement all things from the protocol.
protocol DoSport { var strength: Int { get set} static var instanceCount: Int { get} func walk(distance: Int) }class Dog: DoSport { static var instanceCount: Int = 0func walk(distance: Int) { print("Walk \(distance) with four leg") } var strength: Int init(strength: Int) { self.strength = strength Dog.instanceCount += 1 }}class People: DoSport { static var instanceCount = 0 func walk(distance: Int) { print("Walk \(distance) with two leg") } var strength: Int init(strength: Int) { self.strength = strength People.instanceCount += 1 } }var d = Dog(strength: 200) d.walk(distance: 4000) var p = People(strength: 1000) p.walk(distance: 1000)

Mutating Method Requirements
protocol Togglable { mutating func toggle() }

The mutating keyword only used by structures and enumerations.
Initializer Requirements
protocol SomeProtocol { init(someParameter: Int) }

Class Implementation
The required modifier ensures that all the subclass of the someClass must conform to the protocol and implement the method.
class SomeClass: SomeProtocol { required init(someParameter: Int) {} }

Subclass Override Initializer and Conform to the Protocol
protocol Cuttable { init(someParameter: Int) }class Shape { init(someParameter: Int) {} } class Square: Shape, Cuttable { required override init(someParameter: Int) { super.init(someParameter: someParameter) } }

Failable Initializer Requirements A failable initializer requirement can be satisfied by a failable or nonfailable initializer on a conforming type. A nonfailable initializer requirement can be satisfied by a nonfailable initializer or an implicitly unwrapped failable initializer.
Protocol as Types
Using a protocol as type is sometimes called an existential type. So call “A type T conform to the protocol”.
Use a protocol as a type:
  • 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
Example:
Use the RandomNumberGenerator protocol as a type, the parameter that the initializer get should conform to the RandomNumberGenerator .
protocolRandomNumberGenerator { func random() -> 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 } }class CustomGenerator: RandomNumberGenerator { func random() -> Double { return 3.0 } }// When the CustomGenerator() instance pass to the initialzer, it's been upcasting. var dice = Dice(sides: 2, generator: CustomGenerator()) // Give it a generator. It's a way to make generics. print(dice.roll()) // 7

Delegation
A class has a delegation in itself and the delegation take the place of class to do other things. The delegation’s input is the class whom want to be delegated.
Example: A snake and ladder game. The DiceGame is a protocol that require a Dice and a play() method. It’s the fundamental function of a DiceGame .
The DiceGameDelegate is a protocol that required some method to do the detail of the game, it defines how the game be played. It rely on the DiceGame . So here is the Delegation.
The SnakeAndLadders implements the DiceGame , and implements what to do in the play() method. It has a DiceGameDelegate to do delegate itself to do the detail things.
class Generator { func getNum() -> Int { Int.random(in: 1...6)} }class Dice { let sides: Int var generator: Generator init(sides: Int, generator: Generator) { self.sides = sides self.generator = generator } func roll() -> Int { return generator.getNum() } } 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) }class SnakeAndLadders: DiceGame { let finalSquare = 25 let dice = Dice(sides: 6, generator: Generator()) var square = 0 var board: [Int] init() { board = Array(repeating: 0, count: finalSquare + 1) // Some point in the board have extra square buffer. 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// If the score(square + diceRoll) equal to max, the game end case let newSquare where newSquare > finalSquare: continue gameLoop // If the score(square + diceRoll) is greater than max, try again to get a diceRoll. default: square += diceRoll // If the score(square + diceRoll) is smaller than max, the game loop go on. square += board[square] } } delegate?.gameDidEnd(self) }}class DiceGameTracker: DiceGameDelegate { var numberOfTurn = 0 func gameDidStart(_ game: DiceGame) { numberOfTurn = 0 if game is SnakeAndLadders { print("Start a new game of Snakes and Ladders") } print("Game use a \(game.dice.sides)") }func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { numberOfTurn += 1 print("Rolled a \(diceRoll)") }func gameDidEnd(_ game: DiceGame) { print("The game lasted for \(numberOfTurn) turns") } }// The use of game and the delegation. let tracker = DiceGameTracker()// It's a implementation of the DiceGameDelegate. let game = SnakeAndLadders()// It's a implementation of the DiceGame. game.delegate = tracker game.play()

Adding Protocol Conformance with an Extension
We can extend an existing type to adopt and conform to a new protocol, even if we don’t have access to the source code for the existing type.
protocol TextOut { var textDesc: String { get } }extension Dice: TextOut { var textDesc: String { return "It's a dice with \(self.sides) sides" } }print(game.dice.textDesc)// Print: It's a dice with 6 sides

Conditionally Conforming to a Protocol Condition: Where the element conform the TextOut
extension Array: TextOut where Element: TextOut { var textDesc: String { let itemAsText = self.map{$0.textDesc}// It's a map // Join the map to be a string return "[" + itemAsText.joined(separator: ",") + "]" } }let d6 = Dice(sides: 12, generator: Generator()) let d12 = Dice(sides: 12, generator: Generator()) let myDice = [d6, d12] print(myDice.textDesc) // [It's a dice with 12 sides,It's a dice with 12 sides]

Declaring Protocol Adoption with an Extension Use the instance as a protocol. So it’s the protocol oriented programming.
struct Hamster { var name: String var textDesc: String { return "A Hamster \(name)" } }extension Hamster: TextOut {}let h = Hamster(name: "HH") let p: TextOut = h print(p.textDesc)

Adopting a Protocol Using a Synthesized Implementation
Swift can automatically provide the protocol conformance for Equatable , Hashable, and Comparable in many simple cases.
Synthesized implementation of Equatable
  • Structures that have only stored properties that conform to the Equatableprotocol
  • Enumerations that have only associated types that conform to the Equatableprotocol
  • Enumerations that have no associated types
Synthesized implementation of Hashable .
  • Structures that have only stored properties that conform to the Hashableprotocol
  • Enumerations that have only associated types that conform to the Hashableprotocol
  • Enumerations that have no associated types
Example:
  • Once the structure or enumeration conform the Equatable , it can be compared by ==, and the structure of enumeration needn’t to implement that the protocol required.
    struct Vector3D: Equatable { var x = 0.0, y = 0.0, z = 0.0 } let point = Vector3D(x: 2.0, y: 3.0, z: 4.0) let pointAnother = Vector3D(x: 2.0, y: 3.0, z: 4.0)if point == pointAnother { print("Two vectors are equivalent") }// Print: Two vectors are equivalent

  • Comparable
    enum SkillLevel: Comparable { case beginner case intermediate case expert(stars: Int) }var levels = [SkillLevel.intermediate, SkillLevel.beginner, SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]for level in levels.sorted() { print(level) }// Print: // beginner // intermediate // expert(stars: 3) // expert(stars: 5)

Collection of Protocol Types
A protocol can be used as the type to be stored in a collection such as an array or a dictionary.
let things: [TextOut] = [d6, d12, h] for thing in things { print(thing.textDesc) }// Print: // It's a dice with 6 sides // It's a dice with 12 sides // A Hamster HH

Protocol Inheritance
A protocol can inherit one or more other protocols.
protocol Storeable { func store() -> String } protocol Readable {}protocol Cleanable {}protocol BookShelf: Storeable, Readable, Cleanable { // Here the BookShelf will have the requirement from the protocols. }

Class-Only Protocols
We can limit protocol adoption to class types by adding the AnyObject protocol to a protocol’s inheritance list. It defines that the conforming type has a reference semantics rather than value semantics.
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol { }

Protocol Composition
A type can conform to multiple protocols at the same time. It has the form SomeProtocol & AnotherProtocol .
Protocol composition don’t define any new protocol types.
protocol Named { var name: String { get } }protocol Aged { var age: Int { get} }struct Person: Named, Aged { var name: String var age: Int }// The type passed to the function must conform to both Named and Aged. func goToSchool(menber: Named & Aged) { print("Go to school: \(menber.name) is \(menber.age)") }let person = Person(name: "Mike", age: 8) goToSchool(menber: person)// Print: Go to school: Mike is 8

Combines the Protocol and Class Combines the Named protocol and Location class.
protocol Named { var name: String { get } }func printInfo(location: Location & Named) { print("The place \(location.name)[\(location.latitude), \(location.longitude)] ") }let place = City(name: "Teochew", latitude: 23.67, longitude: 116.62) printInfo(location: place)// Print: The place Teochew[23.67, 116.62]

Checking for Protocol Conformance
It can do the checking and casting as operation that in type checking and casting.
  • is : check a protocol
  • as : cast a protocol to a specific protocol.
    The as? is downcast operator, it return an optional value. The as! is force downcast, it can trigger runtime error.
// Protocol is used to describe some status. protocol HasArea { var area: Double { get } } class Circle: HasArea { let pi = 3.1415927 var radius: Double var area: Double { return pi * radius * radius} init(radius: Double) { self.radius = radius } }class Square: HasArea { var area: Double init(area: Double) { self.area = area } }class Animal { var legs: Int init(legs: Int) { self.legs = legs } }let objs: [Any] = [Circle(radius: 5), Square(area: 20), Animal(legs: 4)]for obj in objs { if obj is HasArea { print(obj, " is HasArea") } else { print(obj, " is not HasArea") } }for obj in objs { if let object = obj as? HasArea { // Here the object is know to be of type HasArea, so it has the only property 'area' to access. print(object, " can be cast to a HasArea. Area is \(object.area)") } else { print(obj.self, " can't be cast to a HasArea") } }// Print: // Page_Contents.Circleis HasArea // Page_Contents.Squareis HasArea // Page_Contents.Animalis not HasArea // Page_Contents.Circlecan be cast to a HasArea Area is 78.5398175 // Page_Contents.Squarecan be cast to a HasArea Area is 20.0 // Page_Contents.Animalcan't be cast to a HasArea

Optional Protocol Requirements
Use the @objc to mark the protocol and the requirements. So these requirements don’t have to be implemented by types that conform to the protocol.
@objc :
  • Can be adopted only by classes that inherit from Objective-C class or other @objc classes (Maybe in the new swift version, the class don’t have to inherit the OC class).
  • Can’t be adopted by structures or enumerations.
  • The type used in the optional requirement will automatically be an optional. Such as (Int) -> String becomes ((Int) -> String)? . Here the entire function type is wrapped in the optional, not the method’s return value.
@objc protocol Source { @objc optional func increment(count: Int) -> Int @objc optional var anotherIncrement: Int { get } }class Counter { var count = 0 var source: Source? func increment() { if let amount = source?.increment?(count: count) { count += amount } else if let amount = source?.anotherIncrement { count += amount } } }class ThreeSource: Source { let anotherIncrement = 3 }class TowardZeroSource: Source { func increment(count: Int) -> Int { if count == 0 { return 0 } else if count < 0 { return 1 } else { return -1 } } }let counter = Counter() counter.source = ThreeSource() for i in 1...3 { counter.increment() print(counter.count) }print()counter.source = TowardZeroSource() for _ in 1...10 { counter.increment() print(counter.count) }print()counter.count = -5 for _ in 1...8 { counter.increment() print(counter.count) }// Print: // 3 // 6 // 9 // // 8 // 7 // 6 // 5 // 4 // 3 // 2 // 1 // 0 // 0 // // -4 // -3 // -2 // -1 // 0 // 0 // 0 // 0

The instance of the Counter use different source of the count, so that it has the different effects.
Protocol Extensions
Protocol can be extended to provide method, initializer, subscript, and computed property implementations to conforming types. But can’t make a protocol inherit from another, the inheritance always specified in the protocol declaration itself.
extension RandomNumberGenerator { func randomBool() -> Bool { returnDouble.random(in: 0...1) > 0.5 } }let generator = SystemRandomNumberGenerator() print(generator.randomBool())

The code above show that the class SystemRandomNumberGeneraror conform the RandomNumberGenerator protocol and we extend the RandomNumberGenerator protocol with a randomBool() method, so the SystemRandomNumberGeneraror can use the new method easily.
Providing Default Implementations We can provide method or computed property requirement in the protocol’s extension.
protocol HasLeg { var leg: Int { get} var desc: String { get } }extension HasLeg { var desc: String { return "I have \(leg) legs" } }class Dog: HasLeg { var leg: Int //var desc: String init() { leg = 4 } } let dog = Dog() print(dog.desc) // I have 4 legs

Adding Constraints to Protocol Extensions 【Swift Learning Summary: Protocols】Here we constrain the extension of the protocol only add extension when the condition Element: Equatable satisfied.
extension Collection where Element: Equatable { func allEqual() -> Bool { for element in self { if element != self.first { return false } } return true } }let a = [100, 100, 100, 100] let b = [100, 100, 200, 100]print(a.allEqual()) print(b.allEqual())

    推荐阅读