规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的
东西。类、结构体 或枚举都可以遵循协议,并为协议定义的这些要求
提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型
遵循这个协议。除了遵循协议的类型必须实现的要求外,还可以对协议
进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵
循协议的类型就能够使用这些功能;
协议语法
protocol SomeProtocol {
// 这里是协议的定义部分
}
遵守协议的格式
class SomeClass: SomeSuperClass, FirstProtocol,AnotherProtocol {
// 类的内容
// 实现协议中的方法
}
注意点:
1:要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加
上协议名称,中间以冒号(:)分隔。遵循 多个协议时,各协议之间用逗号
(,)分隔;
2:拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔;
属性要求
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属
性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名
称和类型。此外,协议还指定属性是可读的还是可读可写的;
例子
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
协议中定义类型属性
协议中定义类型属性时,总是使用static关键字作为前缀。当类类型遵
循协议时,除了static关键字,还可以使用class关键字来声明类型属性;
例子
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"
方法要求
1:协议可以要求遵循协议的类型实现某些指定的实例方法或类方法,协议
中的方法不支持为协议中的方法的参数提供默认值;
2:在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类
型遵循协议时,除了static关键字,还可以使用class关键字作为前缀;
例子
protocol SomeProtocol {
static func someTypeMethod()
func random() -> Double
}
Mutating方法要求
在需要在方法中改变方法所属的实例,在值类型(即结构体和枚举)的实
例方法中,将mutating关键写在func关键字之前,表示可以在该方法中
修改它所属的实例以及实例的任意属性的值;
注意点:
实现协议中的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
}
}
构造器要求
协议可以要求遵循协议的类型实现指定的构造器;
例子1
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 这里是构造器的实现部分
}
}
在遵循协议的类中实现构造器,无论是作为指定构造器,还是便利构
造器。你都必须为构造器实现标上required修饰符;
例子2
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 这里是构造器的实现部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol { // 因为遵循协议,需要加上 required
// 因为继承自父类,需要加上 override
required override init() {
// 这里是构造器的实现部分
}
}
一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的
要求,那么该构造器的实现需要同时标注required和override修饰符;
可失败构造器要求
遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来满足
协议作为类型
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用;
应用场景
1:作为函数、方法或构造器中的参数类型或返回值类型;
2:作为常量、变量或属性的类型;
3:作为数组、字典或其他容器中的元素类型;
注意点
协议是一种类型,因此协议类型的名称应与其他类型(例如
Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法;
通过扩展添加协议一致性
1:即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器;
2:通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同;
注意点:
通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获
得协议中定义的各项功能;
类类型专属协议
协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵
循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协
议的继承列表中,在其他继承的协议之前;
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 这里是类类型专属协议的定义部分
}
协议合成
有时候需要同时遵循多个协议,你可以将多个协议采用
SomeProtocol&AnotherProtocol这样的格式进行组合,称为 协议合成
(protocol composition)。你可以罗列任意多个你想要遵循的协议,以与
符号(&)分隔;
例子
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let p = Person(name: "枫叶", age: 18)
wishHappyBirthday(to: p)
view.backgroundColor = UIColor.orange
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
wishHappyBirthday(to:)函数的参数celebrator的类型,为Named&Aged,
这意味着它不关心参数的具体类,只要参数符合这两个协议即可;
注意点:
协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求
合成到一个只在局部作用域有效的临时协议中;
}
检查协议一致性
使用is和as操作符来检查协议一致性,即是否符合某协议,并且可以转
换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检
查和转换完全相同;
1:is用来检查实例是否符合某个协议,若符合则返回true,否则返回false。
2:as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil。
3:as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
例子
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
}
}
class Animal {
var legs: Int
init(legs: Int) {
self.legs = legs
}
}
override func viewDidLoad() {
let objects: [AnyObject] = [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)]
}
1:objects数组可以被迭代,并对迭代出的每一个元素进行检查,看它是
否符合HasArea协议,当迭代出的元素符合HasArea协议时,将as?操作符
返回的可选值通过可选绑定,绑定到objectWithArea常量上,objectWithArea是HasArea协议类型的实例;
2:objects数组中的元素的类型并不会因为强转而丢失类型信息,它们仍
然是 Circle,Country,Animal类型。然而,当它们被赋值给
objectWithArea常量时,只被视为HasArea类型,因此只有area属性能够
被访问;
可选的协议要求
协议可以定义可选实现,协议中使用optional关键字作为前缀来定义需要
选择实现的方法,因为这个是OC特性,所以协议和可选实现的方法都必须
带上@objc,标记有@objc特性的协议只能被继承自Objective-C类的类或
者@objc类遵循,其他类以及结构体和枚举均不能遵循这种协议;
swift中的协议默认是必须实现的;
协议扩展
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实
现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每
个遵循协议的类型中都重复同样的实现,也无需使用全局函数;
提供默认实现
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实
现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定
义实现将会替代扩展中的默认实现被使用;
注意点
通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然
在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用;
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足
这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写
在协议名之后,使用where子句来描述;
例子
extension CollectionType where Generator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
}
}
扩展CollectionType协议,但是只适用于集合中的元素遵循了
TextRepresentable协议的情况;
注意点:
如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的
类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多
的那个协议扩展提供的默认实现;
注意点
1:默认情况下在swift中所有的协议方法都是必须实
现的,如果不实现,则编译器会报错;