美文网首页swift
swift 中 Protocol(swift 5.5)

swift 中 Protocol(swift 5.5)

作者: 若水water | 来源:发表于2021-09-29 16:15 被阅读0次
    Protocol 介绍

    协议定义了适合一个特殊任务或功能的 方法,属性,和其他要求的蓝图。协议可以被 class, structure, enumeration 采纳(实现),来提供这些requirements的实现。即遵守协议的类型 要实现 协议中 定义的方法。满足了协议要求的任何类型 被称作 遵守了 该协议。
    遵守协议的类型 除了 实现 指定的要求(requirements)外,你还可以扩展一个协议 来实现 其中的某些 requirements ,或者实现 遵守协议的类型可以利用的 额外的功能。
    因为oc中的协议 中定义的方法 是可以声明为require的,即必须要实现的方法,但是swift中没有这样的声明,所有遵守 协议的类型,协议中所定义的方法都要实现。但是如果某些方法认为不是必要实现的,可以通过扩展协议的方式,来实现某些 不必须实现的方法(即oc 中标注的 optional方法),也就是对某些方法提供默认实现,那么遵守协议的类型 就不用再次实现这些方法。
    这在扩展中有讲解到:https://www.jianshu.com/writer#/notebooks/48753120/notes/92016058

    协议语法

    protocol 关键字 + 协议名字

    protocol SomeProtocol {
        // protocol definition goes here
    }
    

    自定义类型遵守某个协议,协议的名字写在类型名后面用冒号分割,协议可以多继承,各个协议之间用逗号分割

    struct SomeStructure: FirstProtocol, AnotherProtocol {
        // structure definition goes here
    }
    

    如果一个类有父类,那么先写父类(写在类名之后,以冒号分割)再写协议名字,以逗号分割

    class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
        // class definition goes here
    }
    
    • 属性要求
      Protocol 可以要求任何遵守该协议的类型 提供 一个实例属性 或类型属性 通过一个特殊的名字和类型。protocol 没有指定这个属性是存储属性还是计算属性,它只指定所需的这个属性的名字和类型。还指定这个属性必须是只读的还是可读可写的。
      如果协议要求属性是可读可写的,那么常量存储属性或只读计算属性无法满足该协议的要求。如果协议要求一个属性是可读的,那么任何一个属性都能满足该要求,属性被设置成 也 可写的 这也是有效的。
      协议要求属性总是声明为可变的,使用关键字var声明。可读可写的属性 要在类型声明后面加上{ get set }。可读属性的声明要加{ get }
    protocol SomeProtocol {
        var mustBeSettable: Int { get set }
        var doesNotNeedToBeSettable: Int { get }
    }
    

    当你在协议中定义类型属性时,总是要求以static关键字作为前缀,在类中实现类型属性时,可以以class 或者 static作为前缀(但是注意:在class 中 声明类型存储属性时只能用 static,类型计算属性可以用 class)

    protocol AnotherProtocol {
        static var someTypeProperty: Int { get set }
    }
    
    class MyClass: AnotherProtocol {
        //类型 存储 属性 在class 中 只能用 static
        static var someTypeProperty: Int = 0
        //类型计算属性 可以用 class
        class var name: String {
            return ""
        }
    }
    

    下面看一个协议,只有一个实例属性

    protocol FullyNamed {
        var fullName: String { get }
    }
    

    此协议要求符合类型提供一个全名,此协议指定了 符合类型必须提供一个可读的实例属性,名称为fullName,类型是string类型
    下面是一个结构体 实现了该协议:

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

    以上结构体Person的每一个实例都有唯一一个 类型为String的 存储属性,这符合了FullyNamed协议的要求,说明Person正确的实现了FullyNamed协议,如果不满足协议要求,会有编译时错误
    以下 是一个类实现了该协议,实现协议中的属性fullName,是作为一个只读的计算属性实现的。

    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")
    
    方法要求

    协议中可以指定 实例方法 和类型方法,让符合的类型去实现。这些方法作为协议声明的一部分与 正常的 实例 和类型 方法 的写法一样。但是没有花括号和方法体,也就是没有方法的实现部分,只有声明。允许有可变参数,遵循和正常的方法一样的规则,但是不能为协议定义中的方法参数指定默认值。
    和类型属性的要求一样,在类型方法前 添加关键字static,当被class实现的时候,也可以使用 staticclass关键字

    protocol AnotherProtocol {
        static var someTypeProperty: Int { get set }
        //类型方法 使用 static 关键字
        static func someTypeMethod()
    }
    
    class MyClass: AnotherProtocol {
        //类型 存储 属性 在class 中 只能用 static
        static var someTypeProperty: Int = 0
        //类型计算属性 可以用 class
        class var name: String {
            return ""
        }
        //可以使用 class 关键字
        class func someTypeMethod() {
            
        }
    }
    

    下面的例子是在协议中声明了一个实例方法, RandomNumberGenerator协议要求任何符合的类型必须提供一个实例方法,名字为random,无参数,返回值类型为Double.RandomNumberGenerator协议不对每个随机数的生成做任何假设,只是简单的要求生成器提供一个标准的方式来生成一个随机数。

    protocol RandomNumberGenerator {
        func random() -> Double
    }
    
    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()
    
    • Mutating方法
      对于一个方法来说,有时候更改它所属于的实例 是必要的。对于值类型(尤其是 structenum),你应该在方法声明fun前 添加关键字mutating,来指明这个方法被允许 修改它所属实例的任何属性。如果你定义了一个协议实例方法,旨在要改变 采用该协议的任何类型的 实例。应该在协议前添加 mutating关键字作为协议定义的一部分。这使得结构体和枚举能够采用该协议,并满足它的要求。
      下面例子定义了一个协议名称为Togglable,该协议定义了一个实例方法toggle,根据方法名称可以猜测,该方法旨在切换或反转任何符合类型的状态,通常是通过修改该类型的属性。所以toggle()方法使用mutating关键词修饰,指明了该方法被调用的时候会改变符合实例的属性。
    protocol Togglable {
        mutating func toggle()
    }
    

    如果这个协议被结构体 和枚举 实现的话,那么结构体 或者枚举就要实现这个toggle()方法,并且也得用mutating关键词修饰。
    下面的例子就是 名为OnOffSwitch的枚举实现了以上协议。要在枚举的两个状态之间切换。并且由mutating关键词修饰。

    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()
    
    初始化 要求

    协议可能需要指定的初始化程序被符合的类型实现。协议中的初始化程序的定义和正常类型中的初始化定义的方式一样,但是没有大括号和方法体

    protocol SomeProtocol {
        init(someParameter: Int)
    }
    
    Class类型 实现协议中的初始化程序

    你可以在一个符合的类中去实现协议的初始化要求,作为指定构造器或者便利构造器。你必须在 该初始化方法之前 使用required关键词

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

    required关键词的使用 确保你在符合的类 类型的所有子类上提供了一个初始化程序的 显示 或继承的实现,以便他们也能符合协议。
    如果一个子类从父类那里继承了一个特定的初始化方法,并且也实现了 来自协议的 一个匹配的 初始化方法,那么在这个方法前 使用两个修饰符requiredoverride

    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
        }
    }
    
    作为类型的协议

    协议本身不实现任何功能,尽管如此你还可以使用协议作为一个成熟的类型,使用协议作为类型通常被称作 “存在类型”,也就是 存在一个类型“T”,这个类型“T”实现了该协议
    使用协议作为类型的场景:
    1、作为方法的参数或者返回类型
    2、作为常量,变量或者属性的类型
    3、作为数组,字典或者其他容器中 元素的类型
    例如:

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

    委托是一种设计模式,他能使类或结构体的某些职责移交(或委托)给另一种类型的实例。这种设计模式的实现是通过定义一个协议 封装要委托的职责,从而保证实现类型(也成为委托)能提供已经委托的功能。委托可以用来响应特定的操作,或者从外部资源检索数据而不需要知道该源的底层类型。

    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只能被Class类型采纳,为了避免强引用循环,delegate 都会被声明 weak弱引用,一个只适用于类的协议,要使用关键字AnyObject

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

    如上例:SnakesAndLadders类采用了 DiceGame协议,按协议要求 提供了一个 可读的 dice属性和一个 paly()方法(dice属性被声明成了常量,因为初始化后不需要改变,协议只要求必须是可读的),可以看到delegate属性被声明成了 可选的 DiceGameDelegate类型,因为玩游戏 delegate 并不是必须的。因为它是可选类型,所以会被自动的设置初始值为nil。此后游戏实例化器可以选择的将属性设置为合适的委托。因为协议DiceGameDelegate是仅适用于类的,为了防止循环引用,设置delegate 为 weak弱引用。
    DiceGameDelegate协议 声明了三个方法来追踪游戏的进度,这三个方法已经被整合进了play()方法中的游戏逻辑中,当一个游戏开始或结束的时候被调用。因为delegate属性是可选的DiceGameDelegate类型,所以当play()方法每次调用delegate上的方法时都使用可选链(即?),如果 delegate为nil,则会正常的调用失败而不会报错,
    下面这个例子,声明了一个 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")
        }
    }
    

    使用

    let tracker = DiceGameTracker()
    let game = SnakesAndLadders()
    game.delegate = tracker
    game.play()
    
    使用扩展添加协议一致性

    您可以扩展一个存在类型去采用和符合新的协议,即便您无权访问现有类型的源代码,扩展可以为现有类型添加新的属性,方法 和下标。因此可以添加协议可能要求的任何要求。
    例如 下面这个协议TextRepresentable可以被任何能够表示为文本的类型实现,这可能是类型本身的描述,也可以是当前状态的文本版本。

    protocol TextRepresentable {
        var textualDescription: String { get }
    }
    
    extension Dice: TextRepresentable {
        var textualDescription: String {
            return "A \(sides)-sided dice"
        }
    }
    
    有条件的遵守协议

    一个类型可能只在特定的条件下满足协议的要求,比如当类型的泛型参数符合协议时。你可以通过列出约束条件使这个类型有条件的符合协议,当扩展这个类型时。在你符合的协议名称后面 写 这些约束条件 通过 where关键词。
    下面这个例子就是让Array 符合了协议TextRepresentable,只要它存储的元素类型符合TextRepresentable协议

    
    class Dice: TextRepresentable {
        var textualDescription: String = "enen"
    }
    
    
    ///有条件的遵守协议
    extension Array: TextRepresentable where Element: TextRepresentable {
        var textualDescription: String {
           
            let itemsAsText = self.map {
                $0.textualDescription
            }
            return "[" + itemsAsText.joined(separator: ",") + "]"
        }
    }
    //使用
    let a1 = Dice()
    let myDice: Array = [ a1 ]
    var text = myDice.textualDescription
    
    使用扩展的方式 采纳协议

    如果一个类型符合了协议的所有要求,但是还没有声明采用该协议,你可以让它采用这个协议,通过空的扩展

    struct Hamster {
        var name: String
        var textualDescription: String {
            return "A hamster named \(name)"
        }
    }
    
    extension Hamster: TextRepresentable {}
    //使用
    let simoTheHamster = Hamster(name: "Simon")
    let somethingTextRespresentable: TextRepresentable = simoTheHamster
    
    采用使用综合实现的协议

    在一些简单的情况下,swift可以自动提供Equatable,Hashable,Comparable 方法实现,这时候你不用再写一些重复的样板代码来实现这些协议。
    swift 为以下自定义类型提供了Equatable协议的综合实现。

    • 结构体,只有存储属性,并且属性 符合 Equatable协议(也就是存储属性 的类型必须实现了Equatable 协议)
    • Enumerations(枚举),只有关联类型,并且关联类型符合Equatable协议(同上)
    • 没有关联类型的Enumerations(枚举)
      为了能使用操作符==, 在你定义类型的时候声明符合Equatable协议,不用再亲自实现==操作符,Equatable协议提供了 !=方法的默认实现。
      以下的例子定了了Vector3D结构体,是一个三维位置向量(x, y, z),因为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 anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
    if twoThreeFour == anotherTwoThreeFour {
        print("These two vectors are also equivalent.")
    }
    

    swift 提供Hashable协议的综合实现,在以下的几种自定义类型的情况中:

    • Structures(结构体),只有存储属性并且存储属性符合了Hashable协议
    • Enumerations(枚举),只有关联类型,并且关联类型符合了Hashable协议
    • Enumerations(枚举),没有任何关联类型
      为了能接收hash(into:)的综合实现,也就是为了能调用hash(into:)这个方法,在定义类型的时候(在声明这个自定义类型的文件中)要声明符合Hashable协议 (与)Equatable使用方式一样

    swift为没有值类型的枚举 提供Comparable协议的综合实现。如果枚举有关联类型,这个关联类型必须符合Comparable协议。为了能接收<操作符,在你声明自定义枚举类型的时候 声明 符合 Comparable协议。此时就不用再自己去实现<操作符的实现,Comparable协议默认实现<=, >, >=这些其他操作符实现。

    enum SkillLevel: Comparable {
        case beginner
        case intermediate
        //关联类型Int 符合 Comparable协议
        case expert(starts: Int)
    }
    
    var levels = [SkillLevel.intermediate, SkillLevel.beginner,
                  SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
    for level in levels.sorted() { //sorted 方法内部调用了Comparable 中的方法
        print(level)
    }
    
    协议类型的集合

    数组或者字典里可以存储 协议类型的数据元素,例如

    let things: [TextRepresentable] = [game, d12, simonTheHamster]
    
    协议继承

    一个协议可以继承一个或者多个其他的协议,可以在它继承的需求之上添加更多的需求,协议继承的语法和class 继承的语法类似,多个继承的协议之间以,分割

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

    下面的例子,PrettyTextRepresentable协议 继承 TextRepresentable协议

    protocol TextRepresentable {
        var textualDescription: String { get }
    }
    
    protocol PrettyTextRepresentable: TextRepresentable {
        var prettyTextualDescription: String { get }
    }
    

    PrettyTextRepresentable 协议 继承自 TextRepresentable 协议,所有采用PrettyTextRepresentable 协议的类型,都必须满足 TextRepresentable 协议强制执行的所有要求,加上 PrettyTextRepresentable协议强制执行的额外要求,这个例子中 PrettyTextRepresentable 协议添加了一个 可读的属性prettyTextualDescription,返回 String 类型
    SnakesAndLadders类可以使用扩展 符合 PrettyTextRepresentable 协议

    extension SnakesAndLadders: TextRepresentable {
        var textualDescription: String {
            return "A game of Snakes and Ladders with \(finalSquare) squares"
        }
    }
    
    extension SnakesAndLadders: PrettyTextRepresentable {
        var prettyTextualDescription: String {
            var output = textualDescription + ":\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
        }
    }
    
    仅适用于Class 类型的协议

    你可以限制一个协议的采用者为Class类型,而不是 结构体也不是枚举,方式是:在协议继承列表中 添加 AnyObject协议
    例如:

    protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
        // class-only protocol definition goes here
    }
    
    协议组合

    有时候一个类型会同时实现多个协议,你可以使用协议组合 将多个协议 组合成 一个需求,协议组合就好像是 你定义了一个临时的本地协议,该协议 具有组合中 所有的协议,协议组合 不会定义任何新的协议要求
    协议组合的形式 是 SomeProtocol & AnotherProtocol。你可以列举出你需要的尽可能多的协议。以&分割。
    协议组合 除了是 将多个协议组合在一起的方式外,还可以包含一个Class 类型,代表父类。

    下面的例子,将两个 名为Named 和Aged 协议 组合成一个对函数参数的单一的协议组合要求

    protocol Named {
        var name: String { get }
    }
    protocol Aged {
        var age: Int { get }
    }
    struct Person: Named, Aged {
        var name: String
        var age: Int
    }
    
    func wishHappyBirthday(to celebrator: Named & Aged) {
        print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
    }
    //使用
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    wishHappyBirthday(to: birthdayPerson)
    

    以上的例子 中定义了一个方法wishHappyBirthday(to:),该方法接收一个参数类型为Named & Aged,意思就是 “任何同时符合了Named 和Aged 两个协议的类型”,至于哪种特定类型被传入了方法并不重要,只要它实现这两个被要求的协议。

    下面 是一个将上例中 Named协议和 Location类组合在一起的例子:

    class Location {
        var latitude: Double
        var longitude: Double
        init(latitude: Double, longitude: Double) {
            self.latitude = latitude
            self.longitude = longitude
        }
    }
    class City: Location, Named {
        var name: String
        init(name: String, latitude: Double, longitude: Double) {
            self.name = name
            super.init(latitude: latitude, longitude: longitude)
        }
    }
    func beginConcert(in location: Location & Named) {
        print("Hello, \(location.name)!")
    }
    //使用
    let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
    beginConcert(in: seattle)
    

    beginConcert(in:)方法需要一个 Location & Named类型作为参数,也就是 一个类型 是Location的子类 同时它也实现了Named协议,在这个例子中City类满足了这两个要求

    检查协议一致性

    你可以使用isas操作符来检查协议一致性,并且转换成一个特定的协议。检查和转换成一个协议的语法和 检查和转换成一个类型的 语法是一样的。

    • 如果 is操作符返回 true ,说明 这个对象 遵守了这个协议,如果为false 就是没有遵守
    • as? 会返回一个 协议类型的可选值,如果该对象没有遵守这个协议 就会返回nil
    • as!会强制转换成协议类型,如果转换不成功 就会发生运行时错误
      下面例子定义了一个名字为HasArea的协议,有两个类CircleCountry,这两个类都遵守了HasArea协议。
    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 Country: HasArea {
        var area: Double
        init(area: Double) { self.area = area }
    }
    

    Circle 实现了HasArea协议中的要求,area 作为计算属性实现的,而Country类中 area 作为存储属性实现了协议的要求。
    下面Animal类 没有遵守 HasArea 协议

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

    Circle, Country , Animal 类并没有共享的基类,尽管如此 他们都是类类型,所以这些类型的实例都可以被用来初始化一个存储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 {
            print("Area is \(objectWithArea.area)")
        } else {
            print("Something that doesn't have an area")
        }
    }
    
    可选的协议要求

    你可以为协议定义可选要求,这些要求并不是必须实现的 对于 遵守这个协议的类型来说,作为协议定义的一部分,可选要求以optional修饰符为前缀,可选要求是可用的,为了方便您编写与OC混编的代码。此时,协议 和 可选方法 都要使用@objc标识符标记。请注意:被@objc标识的协议只能被从Objective-C 类 或其他@objc类继承的类 类型 所采用。这种协议不能被 结构体 和 枚举所采用。
    当你调用可选的方法或属性时,它的类型会自动变成可选类型。例如一个方法类型(Int) -> String会变成((Int) -> String)?,请注意,整个函数的类型都包含在可选项中,而不是方法的返回值中。
    可以 使用可选链调用可选协议要求(协议中的可选方法或者属性),考虑到需求(方法或属性)没有被遵守协议的类型所实现的可能性。在调用的时候,在可选的要求(方法或属性)名称后面添加?,来检查可选方法是否实现。
    下面的例子定义了一个整数计数类Counter,它使用外部数据源来提供它的增量。这个数据源是由 CounterDataSource协议定义的,该协议有两个可选的要求。

    @objc protocol CounterDataSource {
        @objc optional func increment(forCount count: Int) -> Int
        @objc optional var fixedIncrement: Int { get }
    }
    

    该协议定义了一个可选的方法要求和一个可选的属性要求,这些要求为数据源提供了两种不同的方式为Counter实例提供适当的增量。
    Counter有一个可选的CounterDataSource类型的 dataSource属性

    class Counter {
        var count = 0
        var dataSource: CounterDataSource?
        func increment() {
          //调用 可选方法increment,后面要加 ?,检查它是否被实现
            if let amount = dataSource?.increment?(forCount: count) {
                count += amount
            } else if let amount = dataSource?.fixedIncrement {
                count += amount
            }
        }
    }
    

    请注意这里有两个级别的可选链。首先,dataSource可能为nil,所以在它后面标记了一个?号,表明当DataSource 不为nil 的时候 会调用increment(forCount:)方法。其次,尽管dataSource存在,也不能保证它实现了increment(forCount:)方法,因为这是个可选方法,不一定非要去实现。那么 increment(forCount:)方法 没有被实现的可能性也会被可选链处理,只有当increment(forCount:)方法被实现的时候 才会被调用,这就是为什么increment(forCount:)方法也会被?标记的原因。
    因为以上两种情况,increment(forCount:)方法调用可能会失败,这个方法会返回一个可选的Int类型。这是对的,尽管这个方法在协议的定义中 返回的是 非可选 Int类型,尽管有两个可选链,一个接一个,结果仍然会被包装成一个可选值。
    如上代码所示,当调用increment(forCount:)方法的时候,它返回的可选的Int类型 会被解包 赋值给常量amount,如果返回的这个可选的Int 类型确实包含值,也就是dataSource和方法都存在,方法的返回值 也就是 解包后的amount 会被添加到存储属性count里面,完成增量。

    //使用,其实ThreeSource 也可以不继承自NSObject
    //继承自NSObject,是可以当成OC对象,可以使用运行时,可以和OC混编
    class ThreeSource: NSObject, CounterDataSource {
        let fixedIncrement = 3
    }
    
    var counter = Counter()
    counter.dataSource = ThreeSource()
    for _ in 1...4 {
        counter.increment()
        print(counter.count)
    }
    
    协议扩展

    可以扩展协议来为要遵守此协议的类型 提供方法,初始化器,下标,和计算属性的实现。这允许你在协议中定义行为,而不是在每一个类型中或全局函数中 再单独定义。
    例如:RandomNumberGenerator协议可以被扩展来提供一个randomBool()方法

    extension RandomNumberGenerator {
        func randomBool() -> Bool {
            return random() > 0.5
        }
    }
    
    提供默认的实现

    你可以在协议扩展中为 该协议中任何方法,或者计算属性要求提供默认的实现。如果符合类型(要实现该协议的类型)提供了自己所需方法或属性的实现,则将使用该实现 而不是扩展中提供的实现。也就是 在符合类型中 提供的实现将覆盖 扩展中 默认的实现。

    protocol TextRepresentable {
        var textualDescription: String { get }
    }
    
    protocol PrettyTextRepresentable: TextRepresentable {
        var prettyTextualDescription: String { get }
    }
    
    extension PrettyTextRepresentable  {
        var prettyTextualDescription: String {
            return textualDescription
        }
    }
    
    向协议扩展添加约束

    当你定义一个协议扩展的时候,你可以指定符合类型必须满足的约束条件在 扩展的方法和属性可用之前。将这些约束条件写在 你扩展的协议名称之后,通过where通用语法。
    例如你可以为协议Collection定义一个扩展,此扩展适用于任何 元素符合Equatable协议的集合,通过给集合的元素添加Equatable协议,你可以使用==或者!=操作符 来比较两个元素

    extension Collection where Element: Equatable {
        func allEqual() -> Bool {
            for element in self {
                if element != self.first {
                    return false
                }
            }
            return true
        }
    }
    //Array类型 本身遵守了Collection 协议,因为arr 中的元素 为Int,遵守了Equatable 协议
    //所以 此时的 arr 可以调用 allEqual 方法
     let arr: [Int] = [1,2,3]
     let _ =  arr.allEqual()
    
    其他
    • 在实现协议的时候,有两种方式组织你的代码
      1> 使用//MARK: 注释来分割协议实现和其他的代码
      2>使用extension 在类/结构体已有代码外,但在同一个文件内

    相关文章

      网友评论

        本文标题:swift 中 Protocol(swift 5.5)

        本文链接:https://www.haomeiwen.com/subject/cmruiltx.html