swift 中 Protocol(swift 5.5)

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 在类/结构体已有代码外,但在同一个文件内
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容