Swift Learning Summary: Protocols

算法旅人说
• 阅读 641

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 = 0

    func 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 .

protocol  RandomNumberGenerator {
    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 EquatableHashable, 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.Circle  is HasArea
// Page_Contents.Square  is HasArea
// Page_Contents.Animal  is not HasArea
// Page_Contents.Circle  can be cast to a HasArea Area is 78.5398175
// Page_Contents.Square  can be cast to a HasArea Area is 20.0
// Page_Contents.Animal  can'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 {
        return  Double.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

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())
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
梦
4年前
微信小程序new Date()转换时间异常问题
微信小程序苹果手机页面上显示时间异常,安卓机正常问题image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/b691e1230e2f15efbd81fe11ef734d4f.png)错误代码vardate'2021030617:00:00'vardateT
Stella981 Stella981
3年前
KaliTools说明书+BurpSuit实战指南+SQL注入知识库+国外渗透报告
!(https://oscimg.oschina.net/oscnet/d1c876a571bb41a7942dd9752f68632e.gif"15254461546.gif")0X00KaliLinux Tools中文说明书!(https://oscimg.oschina.net/oscnet/
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这