• 二十、Swift 协议 Protocols


    1. 概述

    协议只提供方法的声明,不提供实现。协议可以被类、结构体和枚举实现。

    2. 协议的语法 Protocol Syntax

    定义一个协议:

        protocol SomeProtocol {
          // protocol definition goes here
        }

    如果一个类有父类并且需要遵守某个协议,将父类和协议写在冒号后面:

        class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
          // class definition goes here
        }

    3. 属性的要求 Property Requirements

    如果协议的属性需要实现get/set方法,可以如下实现:

        protocol SomeProtocol {
          var mustBeSettable: Int { get set } // 既要实现set,又要实现get
          var doesNotNeedToBeSettable: Int { get }  // 只要求实现get
        }

    使用 class 关键字表明属性从属于类属性,使用 static 关键字表明属性从属于结构体属性和枚举属性:

        protocol AnotherProtocol {
          class var someTypeProperty: Int { get set }
        }

    下面定义了一个只有一个属性的协议:

        protocol FullyNamed {
          var fullName: String { get }
        }

    定义Person结构体实现上面的协议:

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

    因为Person中定义了一个String类型的Stored属性 fullName,也就是Person结构体完全符合了(conformed)FullyNamed协议。

    下面定义了一个类,同样实现了(或者说符合、一致)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"

    4. 方法的要求 Method Requirements

    协议中方法的参数不能有默认值。

    在协议中,用 class 表示类方法,用 static 表示结构体方法和枚举方法(静态方法)。

        protocol SomeProtocol {
          class func someTypeMethod()
        }

    比如,在协议中定义一个实例方法:

        protocol RandomNumberGenerator {
          func random() -> Double
        }

    LinearCongruentialGenerator 实现了上面的协议:

        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) % m)
            return lastRandom / m
          }
        }
        let generator = LinearCongruentialGenerator()
        println("Here's a random number: (generator.random())")
        // prints "Here's a random number: 0.37464991998171"
        println("And another one: (generator.random())")
        // prints "And another one: 0.729023776863283"
    

    5. 可变方法的要求 Mutating Method Requirements

    十、方法 Methods我们知道,在值类型(结构体和枚举)中,如果方法想要修改自己的实例,需要定义为 mutating 类型。

    如果某个协议中的方法,需要修改实现了这个协议的实例的值,应该将方法定义为mutating 类型。那么,实现了这个协议的枚举和结构体可以修改自己的实例本身。

    注意:在类中,不需要写mutating关键字,mutating关键字为值类型专用。

    定义一个协议:

        protocol Togglable {
          mutating func toggle()
        }

    值类型OnOffSwitch 需要实现这个协议:

        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

    6. 构造器的需求 Initializer Requirements

     协议中可以定义构造器:

        protocol SomeProtocol {
          init(someParameter: Int)
        }

    某个类实现了指定了构造器的协议时,不管是Designated构造器还是Convinence构造器,都需要使用 required 关键字表示:

        class SomeClass: SomeProtocol {
          required init(someParameter: Int) {
            // initializer implementation goes here
          }
        }

    required 关键字确保了所有实现了协议的类/子类都要显示/隐式的实现构造器。具体参见十三、初始化 Initialization

    如果子类覆盖了父类的Designated构造器,而且需要实现一个协议中的required构造器,那么同时写上override和required关键字:

        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
          }
        }

    7. Failable构造器的需求 Failable Initializer Requirements

    协议中可以定义failable initializer requirements,详见十三、初始化 Initialization

    协议中定义了 failable initializer requirements,那么实现了协议的类型中对应的构造器可以使failable 也可以是 非failable的。

    原话: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.

    8. 协议作为一种类型 Protocols as Types

    协议本身不实现任何功能,协议表明了一些代码将要完成的功能。

    因为协议是一种类型,所以你可以在其他类型可以使用的地方使用协议:

    • 1)作为函数、方法、构造器的参数类型和返回值类型。
    • 2)作为常量constant、变量variable、或属性property的类型
    • 3)作为数组、字典和其他容器类型的Items的类型

    注意:因为协议是一种类型,所以和其他类型一样(比如Int, String, Double等)使用大写字母开头。

    下面的例子将协议作为一种类型来使用:

        protocol RandomNumberGenerator {
          func random() -> Double
        }
        class Dice {
          let sides: Int  // sides面的骰子
          let generator: RandomNumberGenerator
          init(sides: Int, generator: RandomNumberGenerator) {
            self.sides = sides
            self.generator = generator
          }
          func roll() -> Int {
            return Int(generator.random() * Double(sides)) + 1
          }
        }

    因为generator属性是RandomNumberGenerator协议类型,所以可以将任何实现了RandomNumberGenerator协议的实例赋值给它,因为确保了它存在generator.random()方法。

    创建一个6面的骰子:

        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) % m)
            return lastRandom / m
          }
        }
    
        var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
        for _ in 1...5 {
          println("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

    9. 代理 Delegation

    代理是一种设计模式,它允许类和结构体将自己的一些任务交给(代理给)其他类型的实例完成。

    下面的例子是在Control Flow.一节的Snakes and Ladders(蛇梯棋)。下面的版本采用一个Dice实例掷筛子,实现DiceGame协议DiceGameDelegate 协议:

    image: ../Art/snakesAndLadders_2x.png

        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)
        }
        class SnakesAndLadders: DiceGame {
          let finalSquare = 25
          let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
          var square = 0
          var board: [Int]
          init() {
            board = [Int](count: finalSquare + 1, repeatedValue: 0)
            board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
            board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
          }
          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)
          }
        }

    因为 SnakesAndLadders 的delegate属性为可选值类型,所以一旦delegate为空时,delegate方法的调用会优雅的结束,而不是报错。如果delegate不为空,那么代理的方法会被调用,并且传入 SnakesAndLadders 实例作为参数。

    下面定义了DiceGameTracker

        class DiceGameTracker: DiceGameDelegate {
          var numberOfTurns = 0
          func gameDidStart(game: DiceGame) {
            numberOfTurns = 0
            if game is SnakesAndLadders {
              println("Started a new game of Snakes and Ladders")
            }
            println("The game is using a (game.dice.sides)-sided dice")
          }
          func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
            ++numberOfTurns
            println("Rolled a (diceRoll)")
          }
          func gameDidEnd(game: DiceGame) {
            println("The game lasted for (numberOfTurns) turns")
          }
        }

    gameDidStart函数在游戏开始时设置numberOfTurns为0,它的参数类型为DiceGame而不是SnakesAndLadders ,所以它能访问DiceGame的内容。

    gameDidStart通过传入的game参数访问dice属性,因为game是协议类型,所以只要传入实现了这个协议的实例就可以了,而不用管是什么游戏。

    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

    10. 使用扩展增加协议  Adding Protocol Conformance with an Extension

    可以使用扩展,给已经实现了某些协议的类型增加新的协议。即使你无法访问这个类型的源代码,也可以给它添加协议。比如下面的例子:

        protocol TextRepresentable {
          func asText() -> String
        }

    之前定义的Dice类可以增加上面的协议:

        extension Dice: TextRepresentable {
          func asText() -> String {
            return "A (sides)-sided dice"
          }
        }

    现在可以使用asText方法了:

        let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
        println(d12.asText())
        // prints "A 12-sided dice"

    同样,也可以给SnakesAndLadders扩展TextRepresentable 协议:

        extension SnakesAndLadders: TextRepresentable {
          func asText() -> String {
            return "A game of Snakes and Ladders with (finalSquare) squares"
          }
        }
        println(game.asText())
        // prints "A game of Snakes and Ladders with 25 squares"

    如果某类型已经实现了协议中的方法,那么只需要使用extention进行声明就可以了:

        struct Hamster {
          var name: String
          func asText() -> String {
            return "A hamster named (name)"
          }
        }
        extension Hamster: TextRepresentable {}

    那么:

        let simonTheHamster = Hamster(name: "Simon")
        let somethingTextRepresentable: TextRepresentable = simonTheHamster
        println(somethingTextRepresentable.asText())
        // prints "A hamster named Simon"

    11. 协议的集合 Collections of Protocol Types

    协议像数组和字典一样,可以作为一种类型存储在集合中:

    let things: [TextRepresentable] = [game, d12, simonTheHamster]

    遍历协议数组:

        for thing in things {
          println(thing.asText())
        }
        // A game of Snakes and Ladders with 25 squares
        // A 12-sided dice
        // A hamster named Simon

    注意thing的类型为TextRepresentable,而不是Dice, DiceGame, Hamster,即使他们的实例的实际类型是Dice, DiceGame, Hamster

    因为thing的类型是TextRepresentable,定义了asText方法,所以调用thing.asText()是安全的。

    12. 协议的继承

    协议可以继承一个或多个其他协议以给协议增加新的需求,协议的继承与类类似,但是需要列出继承来的多个协议,通过逗号隔开。

        protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
          // protocol definition goes here
        }

    下面定义了一个继承了协议TextRepresentable 的协议:

        protocol PrettyTextRepresentable: TextRepresentable {
          func asPrettyText() -> String
        }

    协议PrettyTextRepresentable继承了协议TextRepresentable ,任何实现了协议PrettyTextRepresentable的类型既要满足协议TextRepresentable的要求,也要满足协议PrettyTextRepresentable的要求。

    那么:

        extension SnakesAndLadders: PrettyTextRepresentable {
          func asPrettyText() -> String {
            var output = asText() + ":
    " // 调用了子协议中的asText方法
            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
          }
        }

    那么:

        println(game.asPrettyText())
        // A game of Snakes and Ladders with 25 squares:
        // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

    13. Class-Only 协议

    你可以通过 class 关键字,限制一个协议只能被类类型实现,class关键字必须紧跟在冒号后:

        protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
            // class-only protocol definition goes here
        }

    14. 协议的组合 Protocol Composition

    使用 protocol<SomeProtocol, AnotherProtocol> 形式将多个协议组合起来,成为一个单一的协议组合体(a single protocol composition requirement)。

    比如:

        protocol Named {
          var name: String { get }
        }
        protocol Aged {
          var age: Int { get }
        }
        struct Person: Named, Aged {
          var name: String
          var age: Int
        }
    func wishHappyBirthday(celebrator: protocol
    <Named, Aged>) {   println("Happy birthday (celebrator.name) - you're (celebrator.age)!") }
    let birthdayPerson
    = Person(name: "Malcolm", age: 21) wishHappyBirthday(birthdayPerson) // prints "Happy birthday Malcolm - you're 21!"

    上面的例子定义了一个函数 wishHappyBirthday,它只有一个参数celebrator,这个参数为协议组合体,这个参数的类型并不重要,它只是了Named和Aged两个协议的组合体。

    注意:协议组合体并不是创建了一个新的协议类型,它只是创建了一个临时的组合了所有协议的组合体。

    15. 检查协议的实现 Checking for Protocol Conformance

    你可以使用十七、类型绑定 Type Casting介绍的 is 关键字检查协议的实现,as 关键字将协议绑定到其他类型,语法与十七章相同。

    • 1)如果一个实例实现了协议,is 表达式返回 true,否则返回 false
    • 2)向下绑定操作符 as? 返回一个协议类型的可选值,如果实例没有实现协议,那么返回 nil
    • 3)向下绑定操作符 as 强制向下绑定到一个协议类型,如果绑定失败,会触发运行时错误。

    例如:

        @objc protocol HasArea {
          var area: Double { get }
        }

    注意:

    • 1)只有当协议被定义为 @objc 属性时,才能检查它是否被实现。在Using Swift with Cocoa and Objective-C.中介绍了 @objc 关键字用来表明某个协议会被用于Objective-C代码。即使你的代码不与Objective-C进行交互,如果你想检查协议是否被实现,就应该把它定义为 @objc 属性。
    • 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。

    下面两个类都实现了HasArea协议:

        class Circle: HasArea {
          let pi = 3.1415927
          var radius: Double
          var area: Double { 
          return pi * radius * radius
        }    init(radius: Double) {
           self.radius
    = radius
        } }
    class Country: HasArea {   var area: Double    init(area: Double) {
          self.area
    = area
         } }

    下面一个类没有实现 HasArea 协议:

        class Animal {
          var legs: Int
          init(legs: Int) { 
          self.legs
    = legs
        } }

    Circle, CountryAnimal没有共同的基类,但是他们都是类类型,所以他们的实例类型都可以用 AnyObject 表示:

        let objects: [AnyObject] = [
          Circle(radius: 2.0),
          Country(area: 243_610),
          Animal(legs: 4)
        ]

    检查数组元素是否实现了 HasArea 协议:

        for object in objects {
          if let objectWithArea = object as? HasArea {
            println("Area is (objectWithArea.area)")
          } else {
            println("Something that doesn't have an area")
          }
        }
        // Area is 12.5663708
        // Area is 243610.0
        // Something that doesn't have an area

    无论他们是否实现了HasArea 协议, as? 操作符都是返回可选值类型,这个可选值绑定为 objectWithArea 。注意类型绑定并没有改变数据类型,详见类型绑定一节。

    16. 可选类型协议的要求 Optional Protocol Requirements

    你可以给协议定义可选类型的需求(optional requirements),实现协议的类型可以不实现这个需求,使用 optional 关键字表示。

    optional protocol requirement 可以被可选值链条调用。可选值链条参见十六、Optional Chaining。

    在optional protocol requirement后写一个问号 ? ,比如someOptionalMethod?(someArgument)检查需求是否被实现。当 Optional property requirements 和 optional method requirements 被访问的时候,将永远返回一个合适的可选值,以表明可能没有被实现。

    注意:

    • 1)Optional protocol requirements 只能被定义为 @objc 属性。即使你不语Objective-C 代码进行交互,也要写上 @objc ,以表明你想指定 optional requirements。
    • 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。

    下面的例子定义了一个使用外部数据源进行数量增长的协

        @objc protocol CounterDataSource {
          optional func incrementForCount(count: Int) -> Int  // optional method requirement
          optional var fixedIncrement: Int { get }  // optional property requirement
        }

    下面定义了一个类,它有一个 CounterDataSource 类型的可选值属性dataSource:

        @objc class Counter {
          var count = 0
          var dataSource: CounterDataSource?
          func increment() {
            if let amount = dataSource?.incrementForCount?(count) {
              count += amount
            } else if let amount = dataSource?.fixedIncrement? {
              count += amount
            }
          }
        }

    increment方法使用可选值链条尝试去调用incrementForCount方法。

    注意这里有两层可选值链条:

    第一层中,dataSource可能为 nil ,所以dataSource后有个问号表明只有当dataSourse不为nil时才调用 incrementForCount。

    第二层中,即使 dataSource 存在,也不能保证它实现了 incrementForCount 方法,因为这个方法是 optional requirement ,所以 incrementForCount 方法后还写了个问号。

    因为 incrementForCount 方法有上面两个可能调用失败的原因,所以不管它的返回值是不是定义为可选值类型,它一定返回可选值类型。

    下面定义了数据源类:

        class ThreeSource: CounterDataSource {
          let fixedIncrement = 3
        }

    将ThreeSourse作为一个新的Counter实例的数据源:

        var counter = Counter()
        counter.dataSource = ThreeSource()
        for _ in 1...4 {
          counter.increment()
          println(counter.count)
        }
        // 3
        // 6
        // 9
        // 12

    定义一个更复杂的数据源:

        class TowardsZeroSource: CounterDataSource {
          func incrementForCount(count: Int) -> Int {
            if count == 0 {
             return 0
            } else if count < 0 {
              return 1
            } else {
              return -1
            }
          }
        }

    使用数据源:

        counter.count = -4
        counter.dataSource = TowardsZeroSource()
        for _ in 1...5 {
          counter.increment()
          println(counter.count)
        }
        // -3
        // -2
        // -1
        // 0
        // 0

    参考:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267

    http://c.biancheng.net/cpp/html/2433.html

  • 相关阅读:
    SpringBoot整合WebSocket的客户端和服务端的实现
    Django实现发送邮件
    Python环境搭建
    Hexo+Gitee搭建个人博客
    Chrome浏览器安装离线插件Markdown Here
    TestLink测试用例管理工具使用说明
    【odoo14】【好书学习】odoo 14 Development Cookbook【目录篇】
    【odoo14】【开发侧】权限配置
    【odoo14】【用户侧】权限配置
    【odoo14】【知识点】视图的继承逻辑
  • 原文地址:https://www.cnblogs.com/actionke/p/4271933.html
Copyright © 2020-2023  润新知