协议

类和结构体、枚举都可以遵循协议,并为协议定义的这些要求提供实现
除了遵循协议的类型必须实现外还可以对协议进行扩展,通过扩展来实现一部分要求或者添加附加功能

协议语法

协议的定义:

protocol SomeProtocol {
    // 这里是协议的定义部分
}

自定义类型遵循某个协议,定义类型时,可以在类型名称后加上协议名称,中间用:分割,多个协议之间用,分割

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 这里是结构体的定义部分
}

拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 这里是类的定义部分
}

属性要求

  • 协议可以指定遵循协议的类型提供特定名称和类型的实例属性或类型属性。
  • 协议不指定属性是存储属性还是计算属性,只指定属性名称和类型
  • 协议指定属性是可读的还是可读写的

注意:

如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。
如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的

协议总是用var关键字来声明变量属性,在类型声明后加上{ set get }来表示属性是可读可写的,可读属性则用{ get }来表示

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

在协议中定义类型属性时,使用static关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字声明类型属性:

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

方法要求

遵循协议的类型实现某些指定的实例方法或类方法

  • 方法像普通方法一样放在协议的定义中,但是不需要大括号和方法体
  • 与普通方法类似可以在方法中提供可变参数,但是不能为参数提供默认值
  • 在协议中定义类方法时,总是使用static关键字为前缀.当类类型遵循协议时,除了static关键字,还可以使用class关键字作为前缀
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) % m)
        return lastRandom / m
    }
}

Mutating方法要求

如果在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加mutating关键字,使得结构体和枚举能够遵循此协议并满足方法要求

注意

在实现协议的mutating方法时,若是类类型则不用写mutating关键字,而若是结构体或枚举必须写mutating关键字

protocol Togglable {
    mutating func toggle()
}

//定义在两种状态间切换的枚举
enum OnOffSwitch: Togglable {
    case Off, On
    mutating func toggle() {
        switch self {
        case Off:
            self = .On
        case On:
            self = .Off
        }
    }
}

构造器要求

协议可以要求遵循协议的类型实现指定的构造器

protocol SomeProtocol {
    init(someParameter: Int)
}

构造器要求在类中的实现

遵循协议的类实现构造器.不论指定构造器或者便利构造器都必须在实现加上required修饰符

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 这里是构造器的实现部分
    }
}

使用required修饰符可以确保所有子类也必须提供此构造器实现,即符合协议

注意:

如果类被标记为final,就不需要在协议构造器中使用required修饰符,因为final类不会有子类

如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么重写上需要同时加上requiredoverride修饰符

protocol SomeProtocol {
    init()
}
class SomeSuperClass {
    init() {
        // 这里是构造器的实现部分
    }
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因为遵循协议,需要加上 required
    // 因为继承自父类,需要加上 override
    required override init() {
        // 这里是构造器的实现部分
    }
}

可失败构造器要求

协议还可以为遵循协议的类型定义可失败构造器要求

遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init或隐式解包可失败构造器(init!)来满足

协议作为类型

协议可以作为功能完备的类型来使用:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

协议作为一种类型,因此与它类型的命名写法相同 应该使用驼峰写法

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

Dice中含有属性generatorRandomNumberGenerator协议类型,因此遵循了RandomNumberGenerator协议类型的实例都可以赋值给generator

在类中有roll的实例方法,调用generatorrandom()方法来生成一个[0.0,1.0)区间内的随机数

委托(代理)模式


委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例.
委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。
委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型


通过扩展添加协议一致性

我们可以通过扩展代码来为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中相应要求

通过扩展使已有类型遵循并符合协议时,该类型的所有实例也会获得协议中定义的各项功能

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

扩展来遵循并符合协议与原始定义中效果相同

有条件遵循协议

泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。

可以通过在扩展类型时列出限制让泛型类型有条件的遵循某协议。在你采纳协议的名字后面写泛型where分句。

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)

通过扩展遵循协议

有时一个类型已经符合了协议的要求,但是没声明采纳该协议,可以通过空的扩展使其采纳该协议:

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

协议类型的集合

协议类型可以在数组或字典这样的集合使用

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

协议的继承

协议可以继承一个或多个其他协议,可以在继承协议基础上增加新的要求.协议继承语法与类相似,多个协议用逗号分割:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 这里是协议的定义部分
}

类类型专属协议

通过添加AnyObject关键字到协议的继承列表,可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // 这里是类类型专属协议的定义部分
}

协议合成

有时我们需要一个类型遵循多个协议,此时可以使用协议组合来复合多个协议到一个要求中。
协议组合并不定义任何新的协议类型

协议组合采用SomeProtocol & AnotherProtocol的格式进行组合,可以泪如任意数量的协议
除了协议列表,协议组合也能包含类类型,这允许你表明一个需要的父类

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)// “Happy birthday Malcolm - you're 21!”

检查协议一致性

使用isas检查协议的一致性,即是否符合某协议,并且可以转换到指定的协议类型,与类型检查和转换用法相同:

  • is用来检查实例是否符合某个协议,若符合则返回true,否则返回false
  • as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误
protocol HasArea {
    var area: Double { get }
}

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

判断元素是否符合HasArea协议后将as?操作符通过可选绑定,绑定到objectWithArea常量上进行访问

在数据中的元素类型并不会因为强转而丢失类型信息,只是在被赋值为objectWithArea时,被视为HasArea类型,因此只有area属性可以被访问

可选的协议要求

协议可以被定位可选要求,此时遵循协议的类型可以选择是否实现这些要求。

协议中使用option关键字作为前缀来标记可选要求,
可选要求用在需要和OC打交道的代码中
协议和可选要求都必须带上@objc属性
标记@objc特性的协议只能被继承自Objective-C类的类或者@objc类遵循,其他类以及结构体和枚举均不能遵循这种协议

注意:

当使用可选要求时,协议中的可选的方法或属性会自动变成可选的(函数的话整个函数类型是可选的而不是函数的返回值)

可选要求 可以通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似someoptionMethond?(someArgument),可以再可选方法名后加上?调用可选方法(因为此时其方法时可选的而不是其返回值是可选的)

@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
}
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
    //此处无法确定是否存在dataSource 以及 datasource是否遵循了协议 因此需要两层的可选链式调用
        if let amount = dataSource?.incrementForCount?(count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

通过可选链式调用可选协议

CounterDataSource的简单实现

//继承自OC的NSObject
class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

//@objc类
@objc class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的"实现".
此时可以基于协议本身实现这些功能,而不需要给每个遵循协议的类型进行重复的实现或者使用全局函数

//定义一个协议
protocol RandomNumberGenerator {
    func random() -> Double
}
//为协议进行协议扩展
extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

所有遵循该协议的类型 都能获得扩展所增加的方法实现

提供默认实现

可以通过协议扩展来为协议中的的要求属性、方法、下标等提供默认实现。
如果遵循该协议的类型提供了自己的实现,那么这些自定义实现会代替扩展中的默认实现而被使用

为协议扩展添加限制条件

我们上面有条件的遵循了某些协议,除此之外在协议扩展的时候也可以加一些限制条件,此时只有满足这些条件的时候,遵循协议的类型才能获取协议扩展提供的默认实现

将这些限制条件写在扩展协议名字之后,并且使用where字句描述

/**
扩展Collection协议,适用于集合中的元素遵循Equatable协议的情况
*/
extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}
//只有集合中的元素都一致   allEequal方法才返回true

注意:

如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多、最匹配限制的实现