协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类遵循了协议
除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或实现一些附加功能,这样遵循协议的类型就能够使用这些功能
协议语法
协议定义:1
2
3protocol 协议名称 {
// 这里是协议定义部分
}
类型遵循一些,在类型名称后面加上协议名称,中间以冒号(:)分割。遵循多个协议时,各协议之间用逗号(,)分隔
1 | struct 结构体名称: SomeProtocol, AnotherProtocol { |
拥有父类的类型在遵循协议时,应该将父类名放在协议名之前,以逗号分隔
1 | class Child: Person, SomeProtocol, AnotherProtocol { |
属性要求
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的
协议总是用var关键字来声明变量属性,在类型声明后加上{ get set }来表示属性是可读可写的,可读属性则用{ get }来表示
1 | protocol FirstProtocol { |
在协议定义类型属性是,总是使用static关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字来声明类型属性:
1 | protocol SecondProtocol { |
综合:
1 | protocol FullNameProtocol { |
方法要求
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。协议方法不需要大括号和方法体,也可以定义具有可变参数的方法,和普通方法的定义方式相同,但是不支方法的参数提供默认值
定义类方法使用static关键字作为前缀,当类类型遵循协议时,除了static关键字,还可以使用class关键字作为前缀
1 | protocol FuncProtocol { |
mutating方法要求
协议中使用mutating关键字修饰方法,表示可以在方法中修改对象本身以及对象本身属性的值。
注意:实现协议中的mutating方法时,若是类型方法,不用写mutating。而对于结构体和枚举,则必须写mutating关键字
1 | protocol SomeProtocol { |
构造器要求
协议可以要求遵循协议的类型实现指定的构造器。
1 | protocol SomeProtocol { |
类中实现协议的构造器,无论是指定构造器还是便利构造器,都要在方法加上required关键字,确保所有子类也必须提供此构造器实现,也符合协议。
注意:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required关键字,因为final类不能有子类。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注required和override关键字
1 | protocol SomeProtocol { |
协议作为类型
尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
协议可以向其他普通类型一样使用,使用场景:
- 作为函数、方法或构造器中的参数类型或返回值类型
- 作为常量、变量或属性的类型
- 作为数组、字典或其他容器中的元素类型
1 | protocol SomeProtocol { |
委托模式(代理模式)
委托是一种设计模式,允许类或结构体将一些需要它们负责的功能委托给其他对象。
1 | import UIKit |
扩展遵循协议
类类型遵循协议,扩展实现
1 | protocol SomeProtocol { |
有人可能问,要是在SomeClass的括号题中也实现协议的属性&方法会怎样?
1 | protocol SomeProtocol { |
这样的话就会报错:
Invalid redeclaration of ‘describe’和
Invalid redeclaration of ‘fullName()’错误
也就是重复定义了。
总结:类类型遵循协议,类或扩展实现相关属性和方法都可以。
扩展遵循协议
1 | protocol SomeProtocol { |
同样反过来:
1 | protocol SomeProtocol { |
总结:扩展类遵循协议,类或扩展实现相关属性和方法都可以。
协议类型的集合
1 | protocol SomeProtocol { |
协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的需求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔
语法:
1 | protocol SomeProtocol: SomeProtocol1, SomeProtocol2 { |
1 | protocol SomeProtocol1 { |
类类型专属协议
通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协议的继承列表中。
1 | protocol SomeProtocol:class, SomeProtocol1 { |
协议合成
遵循多个协议时,可以采用SomeProtocol
& AnotherProtocol
这样的方式进行组合,成为协议合成(protocol composition)。任意多个协议的时候,以(&)符号分隔
1 | protocol SomeProtocol { |
检查协议一致性
使用is和as操作符来检查协议一致性,即是否符合某个协议。
- is 用来检查对象是都符合某个协议。若符合则返回true,否则返回false
- as? 返回一个可选值,当对象符合某个协议时,返回类型为协议类型的可选值,否则返回nil
- as! 将对象强制向下转型到某个协议类型,如果强转失败,会引发运行时错误
1 | protocol SomeProtocol { |
可选的协议要求
协议的某些属性或方法是可选实现的。遵循协议的类,就可以灵活实现相关属性和方法了。协议中使用optional
关键字作为前缀定义可选类型。因为要跟OC打交道,所以协议以及属性、方法前面都要加上@objc
。那么也推导出只有OC类型的类或者是@objc
类遵循,其他类以及结构体和枚举都不能遵循这种协议。
1 | // 声明一个@objc类的协议 |
协议扩展
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数
1 | protocol SomeProtocol { |
提供默认实现
扩展的协议需要提供实现体,但是遵循协议的类型如果实现了扩展协议的属性或者方法,那么自定义的实现会覆盖扩展协议默认提供的实现。
注意:通过协议扩展为协议提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展的提供实现可以直接调用,而无需使用可选链调用
1 | protocol SomeProtocol { |
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述
1 | protocol SomeProtocol { |
注意:如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
参考: