Swift3.1_协议
简介
规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议。
定义协议
协议的定义方式与类、结构体和枚举的定义非常相似:
protocol SomeProtocol {
// 这里是协议的定义部分
}
要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号:
分隔。遵循多个协议时,各协议之间用逗号,
分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 这里是结构体的定义部分
}
拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是类的定义部分
}
属性要求
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在协议中定义类型属性时,总是使用static
关键字作为前缀。当类类型遵循协议时,除了static
关键字,还可以使用class
关键字来声明类型属性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
如果一个类或者结构体遵循了协议,那么必须包含协议的属性和方法:
struct someStruct: SomeProtocol {
// 遵循 SomeProtocol 协议的属性
var mustBeSettable: Int
var doesNotNeedToBeSettable: Int
// 自己的属性
var myString: String
}
方法要求
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。
protocol MethodProtocol {
func double(_ num: Int) -> Int
}
正如属性要求中所述,在协议中定义类方法的时候,总是使用 static
关键字作为前缀。当类类型遵循协议时,除了static
关键字,还可以使用class
关键字作为前缀:
protocol StaticMethodProtocol {
static func someTypeMethod()
}
如果一个类或者结构体或者枚举遵循了协议,那么必须包含协议的属性和方法:
struct someStruct: SomeProtocol, MethodProtocol {
// 遵循 SomeProtocol 协议的属性
var mustBeSettable: Int
var doesNotNeedToBeSettable: Int
// 自己的属性
var myString: String
// 遵循 MethodProtocol 协议的方法实现
func double(_ num: Int) -> Int {
return num * 2
}
// 自己的方法
func treble(_ num: Int) -> Int {
return num * 3
}
}
Mutating 方法要求
有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating
关键字作为方法的前缀,写在func
关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加mutating
关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
protocol Togglable {
mutating func toggle()
}
当使用枚举或结构体来实现Togglable
协议时,需要提供一个带有 mutating
前缀的toggle()
方法。
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()
// lightSwitch 现在的值为 .On
构造器要求
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol NameProtocol {
var name: String {set get}
init(name: String)
}
构造器要求在类中的实现
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 required
修饰符:
class Person: NameProtocol {
var name: String
required init(name: String) {
self.name = name
}
}
可失败构造器要求
遵循协议的类型可以通过可失败构造器init?
或非可失败构造器init
来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器init
或隐式解包可失败构造器init!
来满足。
协议作为类型
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
协议可以像其他普通类型一样使用,使用场景如下:
- 作为函数、方法或构造器中的参数类型或返回值类型
- 作为常量、变量或属性的类型
- 作为数组、字典或其他容器中的元素类型
protocol TrebleProtocol {
mutating func treble() -> Int
}
class IntNumber: TrebleProtocol {
var num: Int
func treble() -> Int {
return num * 3
}
init(num: Int) {
self.num = num
}
}
class Number {
var trebleNumber: TrebleProtocol
init(trebleNumber: TrebleProtocol) {
self.trebleNumber = trebleNumber
}
}
let intNumber = IntNumber(num: 5)
let number = Number(trebleNumber: intNumber)
print(number.trebleNumber.treble()) // 15
委托(代理)模式
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
声明一个狗的协议,里面有一个方法:
protocol DogDelegate {
func bark(message: String)
}
Dog
类有一个可选属性delegate
,它的类型是DogDelegate
类型。因此在doBark()
方法中通过可选链式调用来调用它的方法。若delegate
属性为nil
,则调用方法会优雅地失败,并不会产生错误。
class Dog {
var name: String
var delegate: DogDelegate?
init(name: String) {
self.name = name
}
func doBark() {
self.delegate?.bark(message: "汪汪汪,我是小狗\(name)")
}
}
定义Person
类,遵循协议DogDelegate
。
class Person: DogDelegate {
var name: String
let dog: Dog = {
return Dog(name: "旺财")
}()
init(name: String) {
self.name = name
self.dog.delegate = self
}
// 实现协议方法
func bark(message: String) {
print("狗对\(name)说的话是: \(message)")
}
}
这样,通过DogDelegate
类型的代理,Person
实例就可以获取到Dog
的叫的内容:
let jay = Person(name: "Jay")
jay.dog.doBark()
// 狗对Jay说的话是: 汪汪汪,我是小狗旺财
解决循环引用
但是这样做是有问题的,jay
强引用dog
,dog
强引用delegate
,然而delegate
又强引用jay
,造成循环引用导致内存泄露:
你需要把delegate
弱引用:
protocol DogDelegate: class {
func bark(message: String)
}
class Dog {
var name: String
weak var delegate: DogDelegate?
init(name: String) {
self.name = name
}
func doBark() {
self.delegate?.bark(message: "汪汪汪,我是小狗\(name)")
}
}
通过扩展添加协议一致性
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。
定义一个数值三倍的协议:
protocol TrebleProtocol {
mutating func treble()
}
让Int
的拓展和Double
的拓展遵循协议,实现协议方法:
extension Int:TrebleProtocol {
mutating func treble() {
self = self * 3
}
}
extension Double: TrebleProtocol {
mutating func treble() {
self = self * 3
}
}
Int
类型的变量和Double
类型的变量都能调用treble()
方法让自身变成三倍:
var num = 3
num.treble() // num = 9
var float = 2.5
float.treble() // num = 7.5
协议类型的集合
协议类型可以在数组或者字典这样的集合中使用,前提是数组或字典内的元素都遵循协议:
let numbers: [TrebleProtocol] = [1, 3, 5, 1.9]
for item in numbers {
var trebleItem = item
trebleItem.treble()
print(trebleItem)
}
// 3
// 9
// 15
// 5.7
协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol MultipleProtocol: TrebleProtocol {
mutating func double()
}
遵循MultipleProtocol
协议的对象就相当于同时遵循了TrebleProtocol
协议:
class Number: MultipleProtocol {
var value = 0
init(value: Int) {
self.value = value
}
func double() {
value *= 2
}
func treble() {
value *= 3
}
}
Number
实例调用double()
方法让属性value
变成当前值的两倍,调用treble()
方法让value
变成当前值的三倍:
let number = Number(value: 3)
number.double()
print(number.value) // 6
number.treble()
print(number.value) // 18
类类型专属协议
你可以在协议的继承列表中,通过添加class
关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class
关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
protocol SomeClassOnlyProtocol: class {
// 这里是类类型专属协议的定义部分
}
当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。
协议合成
有时候需要同时遵循多个协议,你可以将多个协议采用&
这样的格式进行组合,称为 协议合成protocol composition
。
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!”
检查协议一致性
你可以使用is
和as
操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
-
is
用来检查实例是否符合某个协议,若符合则返回true
,否则返回false
。 -
as?
返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
。 -
as!
将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
可选的协议要求
协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用optional
关键字作为前缀来定义可选要求。可选要求用在你需要和Objective-C
打交道的代码中。协议和可选要求都必须带上@objc
属性。标记@objc
特性的协议只能被继承自 Objective-C
类的类或者@objc
类遵循,其他类以及结构体和枚举均不能遵循这种协议。
协议扩展
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
提供默认实现
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。