协议

协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性、以及其他需要的东西。

协议的语法

1
2
3
4
5
6
7
8
9
10
protocol SomeProtocol {
//协议的定义部分
}
//某个自定义类型遵循协议
struct SomeStructure : SomeProtocol, AnotherProtocol {
}
拥有父类的类在遵循协议时,应该将父类名放在协议名之前
class SomeClass : SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是类的定义部分
}

属性要求

协议可以要求遵循的类型提供特定名称和类型的实例属性或类型属性。协议不知道是存储属性还是计算属性,它指定属性的名称和类型

1
2
3
4
5
6
7
8
9
protocol SomeProtocol {
var mustBeSetterble: Int { get set}
var doseNotNeedToBeSettable: Int {get}
}
协议定义类型属性时,总是使用static关键字做前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字来声明类型属性

protocol AnotherProtocol {
static var someTypeProtocol: Int { get set}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//遵循次协议必须有一个可读String类型的实例属性fullName
protocl FullyName {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John")
//john.fullName 为 “John”

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

方法要求

在协议中定义类方法的时候,总是使用static 关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字

1
2
3
4
5
6
7
protocol SomeProtocol {
static func someTypeMethod()
}
//定义一个只含有实例方法的协议
protocol RandomNumberGenerator {
func someFun() -> Int
}
异变方法要求

有时候需要在方法中改变方法所属的实例。例如,在值类型的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改所属的实例以及实例任意属性的值。

注意:
实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须mutating关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protocol Togglable {
mutating func toggle()
}

enum OnOffSwitch: Togglable {
cass off , on
mutating func toggle(){
switch self {
case .off
self = .on
case .on
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
//lightSwitch 现在是 .on
构造器要求

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

1
2
3
protocol SomeProtocol {
init(someParameter: Int)
}
协议构造器要求的类实现

你可以遵循协议的类中实现构造器,无论作为指定构造器还是便利构造器,都必须为构造器标上required修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SomeClass : SomeProtocol {
required init(someParameter: Int){}
}
//required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
//如果类已经标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类

//如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 required 和 override

protocol SomeProtocol{
init()
}
class SomeSuperClass{
init(){}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
//因为遵循协议,需要加上required
//因为继承自父类,需要加上override
required override init(){}
}
可失败构造器要求

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

协议作为类型

协议本身并不实现任何功能,但是协议可以被当做一个成熟的类型来使用。协议可以像其他普通类型一样使用。

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器的元素类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protocol RandomNumberGenerator {
func random() -> Double
}
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
2
3
extersion SomeClass: SomeProtocol {
//实现遵循的协议
}

有条件地遵循协议

泛型类型可能只在某些情况下满足一个协议的要求,比如当类类型的泛型形式参数遵循对应协议时。你可以通过扩展类型列出限制泛型类型有条件的遵循某协议。在你采纳协议的名字后面写泛型where分句。

1
2
3
4
5
6
7
8
9
10
protocol SomeProtocol {
var textOutput: String { get }
}
extension Array : SomeProtocol where Element: String {
//
var textOutput: String{
let text = self.map { $0.textOutput }
return "[" + text + "]"
}
}
在扩展里声明采纳协议

当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时。可以通过空扩展体的扩展采纳该协议。

1
2
3
4
5
struct Hamster {
//完全实现了协议 SomeProtocol
}
//这里显示的声明
extersion Hamster : SomeProtocol{}

注意:即使满足了协议的所有要求,类型也不会自动遵循协议,必须显示地遵循协议

协议类型的集合
1
2
//集合中的3个元素都遵循SomeProtocol协议的实例
let things: [SomeProtocol] = [param1,param2,param3]
协议继承

协议能够继承一个或多个其他协议

1
2
3
protocol SomeProtocol: FirstProtocol,AnthorProtocol {
//定义部分
}

类专属的协议

通过添加AnyObject 关键字到协议的继承列表,就可以限制协议只能被类类型采纳。
protocol SomeClassProtcol: class {
//专属类的协议定义部分
}

协议合成

要求一个类型同时遵循多个协议是很有用的。你可以使用协议组合来复合多个协议到一个要求里。

协议组合使用 SomeProtocol & AnotherProtocol 的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
//函数参数 celebrator 类型为 Name & Aged。这意味着同时要遵循这2个协议
func wishHappyBirthday(to celebrator: Name & Aged){
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
1
2
3
4
5
6
7
class Location {
}
class City: Location, Named {
}
//这意味这方法只接受 Location & Named 的参数
func beginConcert(in location: Location & Named){
}

检查协议一致性

isas操作符来检查协议一致性,即是否复合某协议,并且可以转换到指定的协议类型。

  • is 用来检查实例是否复合某个协议
  • as? 返回一个可选值,当实例复合某个协议是,返回类型为协议类型的可选值。
  • as! 将实例强制向下转换到某个协议,如果转换失败,会引发运行时错误

可选的协议要求

协议可以定义可选要求,遵循协议类型可以选择是否实现这些要求。协议中使用 optional 关键字作为前缀来定义可选要求。可选要求用在你需要和Objective-C打交道的代码中。协议和可选要求都必须带上 @objc 属性。标记 @objc特性的协议只能被继承自 Objective-C类或者遵循。

使用可选要求时,他们的类型会自动变成可选的。比如,一个类型为 (Int) -> String 的方法会变成 ((Int) -> String)? ,这里是函数类型可选,不是函数的返回值可选

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现

1
2
3
4
5
extension RandomNumberGenerator {
func randomBool() -> Bool {
retrun random() > 0.5
}
}

通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外的修改

提供默认实现

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

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足某些限制条件,才能获得协议扩展提供的默认实现。使用where字句来描述

1
2
3
4
5
6
7
8
9
10
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
0%