Swift-协议(protocol)

协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类遵循了协议

除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或实现一些附加功能,这样遵循协议的类型就能够使用这些功能

协议语法

协议定义:

1
2
3
protocol 协议名称 {
// 这里是协议定义部分
}

类型遵循一些,在类型名称后面加上协议名称,中间以冒号(:)分割。遵循多个协议时,各协议之间用逗号(,)分隔

1
2
3
struct 结构体名称: SomeProtocol, AnotherProtocol {
// 这里是结构体的定义部分
}

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

1
2
3
class Child: Person, SomeProtocol, AnotherProtocol {
// 这里是类的定义部分
}

属性要求

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol FirstProtocol {
var name:String { get set }
var mark:Int { get }
}

// FirstClass遵循FirstProtocol协议,那么必须有一个可读可写属性name,一个可读属性mark
class FirstClass: FirstProtocol {
var name: String = "name"
var mark: Int = 33
}

let first = FirstClass()
print("name:\(first.name), mark:\(first.mark)")
first.name = "daisuke"
first.mark = 99
print("name:\(first.name), mark:\(first.mark)")

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

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol SecondProtocol {
static var name:String { get set }
static var age:Int { get }
}

// SecondClass遵循SecondProtocol协议,那么必须有一个可读可写属性name,一个可读属性age
class SecondClass: SecondProtocol {
static var name: String = "daisuke"
static var age: Int = 26

}
print("name:\(SecondClass.name), age:\(SecondClass.age)")
// name:daisuke, age:26

综合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol FullNameProtocol {
var fullName:String { get }
}

// Person遵循FullNameProtocol协议,那么必须有一个可读属性fullName
class Person: FullNameProtocol {
// 实现协议的fullName,并设置为可读
var fullName: String {
return (perfix != nil ? perfix! : "") + " " + name
}
var name:String
var perfix:String?
init(name:String, perfix:String? = nil) {
self.name = name
self.perfix = perfix
}
}

let person = Person(name: "daisuke", perfix: "feng")
print(person.fullName) // feng daisuke
//person.fullName = "hello" // 只读

方法要求

协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。协议方法不需要大括号和方法体,也可以定义具有可变参数的方法,和普通方法的定义方式相同,但是不支方法的参数提供默认值

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
protocol FuncProtocol {
// 类型方法
static func typeMethod()
// 随机方法,返回一个double类型数字
func random() -> Double
// 获取全名方法,返回string类型字符串
func fullName() -> String
// 没有返回值的一个方法
func testFunc()

// 获取全名方法,返回string类型字符串,有参数
func fullName(perfix:String, name:String) -> String
}

class FuncClass: FuncProtocol {
func fullName(perfix: String, name: String) -> String {
print("实现协议的fullName方法")
return perfix + " " + name
}

func testFunc() {
print("实现协议的testFunc方法")
}

static func typeMethod() {
print("实现协议的类型方法")
}

func random() -> Double {
print("实现协议的random方法")
return Double(arc4random() % 100)
}

func fullName() -> String {
print("实现协议的fullName方法")
return "feng daisuke"
}

}

let funcClass = FuncClass()
print(funcClass.random())
//实现协议的random方法
//92.0

print(funcClass.fullName())
//实现协议的fullName方法
//feng daisuke

funcClass.testFunc()
// 实现协议的testFunc方法

FuncClass.typeMethod()
//实现协议的类型方法

print(funcClass.fullName(perfix: "feng", name: "daisuke"))
//实现协议的fullName方法
//feng daisuke

mutating方法要求

协议中使用mutating关键字修饰方法,表示可以在方法中修改对象本身以及对象本身属性的值。

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

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

enum SwitchSelect: SomeProtocol {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}

var light = SwitchSelect.off
light.toggle()
print(light) // on

构造器要求

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

1
2
3
4
5
6
7
8
9
10
protocol SomeProtocol {
init(param:Int, param1:String)
}

class SomeClass: SomeProtocol {
// 实现协议的构造方法,必须加上required
required init(param: Int, param1: String) {
print("实现协议中的构造方法")
}
}

类中实现协议的构造器,无论是指定构造器还是便利构造器,都要在方法加上required关键字,确保所有子类也必须提供此构造器实现,也符合协议。

注意:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required关键字,因为final类不能有子类。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protocol SomeProtocol {
init()
}

class SomeSuperClass {
init() {
// 这里是父类构造器
}
}

class SomeClass: SomeSuperClass, SomeProtocol {
// 因为遵循协议,需要加上required
// 因为重写父类构造器,需要加上override
required override init() {
// 这里是子类构造器
}
}

协议作为类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

协议可以向其他普通类型一样使用,使用场景:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protocol SomeProtocol {
func random() -> Double
}

// 实现SomeProtocol协议的类
class AchieveProtocolClass: SomeProtocol {
func random() -> Double {
return Double(arc4random() % 100)
}
}

class SomeClass {
let name:String
// 声明协议类型属性
let someProtocol:SomeProtocol
// 构造器初始化属性值
init(name:String, someProtocol:SomeProtocol) {
self.name = name
self.someProtocol = someProtocol
}

func roll() -> Int {
return Int(self.someProtocol.random())
}
}

let achieveProtocolClass:AchieveProtocolClass = AchieveProtocolClass()
// achieveProtocolClass实现了SomeProtocol,并且作为someProtocol参数值
let someClass:SomeClass = SomeClass(name: "daisuek", someProtocol: achieveProtocolClass)
print(someClass.roll()) // 92

委托模式(代理模式)

委托是一种设计模式,允许类或结构体将一些需要它们负责的功能委托给其他对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import UIKit

class InitViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

var tableView:UITableView!

lazy var dataArr: [String] = {
return ["1","2","3","4","5","6","7","8","9","10","11","12","13"]
}()

override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height), style: UITableViewStyle.plain)
// 列表的数据&事件委托当前InitViewController来处理
tableView.delegate = self
tableView.dataSource = self
self.view.addSubview(tableView)

/*
UITableViewDelegate协议
public protocol UITableViewDelegate : UIScrollViewDelegate
UITableViewDataSource协议
public protocol UITableViewDataSource : NSObjectProtocol

UITableView:
声明两个协议属性,避免循环引用使用weak声明
weak open var dataSource: UITableViewDataSource?
weak open var delegate: UITableViewDelegate?
那么只要实现这两个协议:
实现数据的来源以及事件的处理就交给InitViewController了
*/

}

// 实现协议的方法
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArr.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellId:String = "cellId"
var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
}
cell?.textLabel?.text = dataArr[indexPath.row]
return cell!
}
}

扩展遵循协议

类类型遵循协议,扩展实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol SomeProtocol {
var describe:String { get }
func fullName() -> String
}

// 类遵循SomeProtocol协议
class SomeClass:SomeProtocol {

}

// 扩展类实现SomeProtocol协议相关属性&方法
extension SomeClass {
var describe: String {
return "extension SomeClass describe"
}
func fullName() -> String {
return "feng daisuke"
}
}

let someClass = SomeClass()
print(someClass.describe) // extension SomeClass describe
print(someClass.fullName()) // feng daisuke

有人可能问,要是在SomeClass的括号题中也实现协议的属性&方法会怎样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protocol SomeProtocol {
var describe:String { get }
func fullName() -> String
}

// 类遵循SomeProtocol协议
class SomeClass:SomeProtocol {
var describe: String {
return "extension SomeClass describe"
}
func fullName() -> String {
return "feng daisuke"
}
}

// 扩展类实现SomeProtocol协议相关属性&方法
extension SomeClass {
var describe: String {
return "extension SomeClass describe"
}
func fullName() -> String {
return "feng daisuke"
}
}

let someClass = SomeClass()
print(someClass.describe) // extension SomeClass describe
print(someClass.fullName()) // feng daisuke

这样的话就会报错:
Invalid redeclaration of ‘describe’和
Invalid redeclaration of ‘fullName()’错误
也就是重复定义了。

总结:类类型遵循协议,类或扩展实现相关属性和方法都可以。

扩展遵循协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol SomeProtocol {
var describe:String { get }
func fullName() -> String
}

class SomeClass {

}

extension SomeClass:SomeProtocol {
var describe: String {
return "extension SomeClass describe"
}
func fullName() -> String {
return "feng daisuke"
}
}

let someClass = SomeClass()
print(someClass.describe) // extension SomeClass describe
print(someClass.fullName()) // feng daisuke

同样反过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol SomeProtocol {
var describe:String { get }
func fullName() -> String
}

class SomeClass {
var describe: String {
return "extension SomeClass describe"
}
func fullName() -> String {
return "feng daisuke"
}
}

extension SomeClass:SomeProtocol {

}

let someClass = SomeClass()
print(someClass.describe) // extension SomeClass describe
print(someClass.fullName()) // feng daisuke

总结:扩展类遵循协议,类或扩展实现相关属性和方法都可以。

协议类型的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protocol SomeProtocol {
var describe:String { get }
}

class SomeClass1: SomeProtocol {
var describe: String {
return "SomeClass1 describe"
}
}

class SomeClass2: SomeProtocol {
var describe: String {
return "SomeClass2 describe"
}
}

class SomeClass3: SomeProtocol {
var describe: String {
return "SomeClass3 describe"
}
}

let someClass1 = SomeClass1()
let someClass2 = SomeClass2()
let someClass3 = SomeClass3()
// 三个雷、类都遵循SomeProtocol协议,数组存放的元素是SomeProtocol类型
let someClassArr:[SomeProtocol] = [someClass1, someClass2, someClass3]
for item in someClassArr {
print(item.describe)
}
//SomeClass1 describe
//SomeClass2 describe
//SomeClass3 describe

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的需求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔

语法:

1
2
3
protocol SomeProtocol: SomeProtocol1, SomeProtocol2  {
// 这里是协议的定义部分
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protocol SomeProtocol1 {
var name:String {get set}
}

// 协议SomeProtocol2继承SomeProtocol1
protocol SomeProtocol2: SomeProtocol1 {
var age:Int {get}
}

// SomeClass遵循SomeProtocol2协议,因为SomeProtocol2继承SomeProtocol1,那么SomeClass遵循SomeProtocol1协议
class SomeClass: SomeProtocol2 {
var age: Int = 0
var name: String = ""
}

类类型专属协议

通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协议的继承列表中。

1
2
3
protocol SomeProtocol:class, SomeProtocol1 {
// 这里是类类型专属协议的定义部分
}

协议合成

遵循多个协议时,可以采用SomeProtocol & AnotherProtocol这样的方式进行组合,成为协议合成(protocol composition)。任意多个协议的时候,以(&)符号分隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol SomeProtocol {
var name:String { get }
}

protocol AnotherProtocol {
var age:Int { get }
}

class SomeClass: SomeProtocol, AnotherProtocol {
var name: String
var age: Int
init(name:String, age:Int) {
self.name = name
self.age = age
}
}

func testProtocolComposition(to param: SomeProtocol & AnotherProtocol) {
print(param.name + " \(param.age)")
}

let someClass = SomeClass(name: "daisuke", age: 26)
testProtocolComposition(to: someClass) // daisuke 26

检查协议一致性

使用is和as操作符来检查协议一致性,即是否符合某个协议。

  • is 用来检查对象是都符合某个协议。若符合则返回true,否则返回false
  • as? 返回一个可选值,当对象符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as! 将对象强制向下转型到某个协议类型,如果强转失败,会引发运行时错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protocol SomeProtocol {
var name:String { get }
}

class SomeClass1: SomeProtocol {
var name: String = "SomeClass1"
}

class SomeClass2: SomeProtocol {
var name: String = "SomeClass2"
}

class SomeClass3 {
var name: String = "SomeClass3"
}

// 任何类型的数组
let someClass1 = SomeClass1()
let someClass2 = SomeClass2()
let someClass3 = SomeClass3()
let classArray:[AnyObject] = [someClass1, someClass2, someClass3]
for object in classArray {
if let someObject = object as? SomeProtocol {
print(someObject.name + "是SomeProtocol类型")
} else {
print("class不是SomeProtocol类型")
}

// if let someObject = object as! SomeProtocol {
// print(someObject.name + "是SomeProtocol类型")
// } else {
// print("class不是SomeProtocol类型")
// }
// 使用as!进行请转会发生运行错误,因为SomeClass3不是SomeProtocol类型,而进行了强转

// if object is SomeProtocol {
// print("class是SomeProtocol类型")
// } else {
// print("class不是SomeProtocol类型")
// }

}
//SomeClass1是SomeProtocol类型
//SomeClass2是SomeProtocol类型
//class不是SomeProtocol类型

可选的协议要求

协议的某些属性或方法是可选实现的。遵循协议的类,就可以灵活实现相关属性和方法了。协议中使用optional关键字作为前缀定义可选类型。因为要跟OC打交道,所以协议以及属性、方法前面都要加上@objc。那么也推导出只有OC类型的类或者是@objc类遵循,其他类以及结构体和枚举都不能遵循这种协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 声明一个@objc类的协议
@objc protocol SomeProtocol {
@objc optional func totalScore(math:Int, chinese:Int) ->Int
@objc optional var math:Int { get }
@objc optional var chinese:Int { get }
}

// 遵循SomeProtocol协议,并实现math、chinese两个属性
class ClassToProtocol: SomeProtocol {
var math: Int = 99
var chinese: Int = 98
}

class SomeClass {
var score:Int = 0
var someProtocol:SomeProtocol?
func incrementScore() {
if let number = someProtocol?.math {
score += number
}
if let number = someProtocol?.chinese {
score += number
}
}
func getTotalScore() {
if let number = someProtocol?.totalScore?(math: 88, chinese: 77) {
score += number
}
}
}

let someClass = SomeClass()
someClass.incrementScore()
print(someClass.score) // 0

// 创建一个classToProtocol类
let classToProtocol = ClassToProtocol()
// 赋值给someClass的协议类型someProtocol
someClass.someProtocol = classToProtocol
someClass.incrementScore()
print(someClass.score) // 197

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protocol SomeProtocol {
func randomDouble() ->Double
}

class SomeClass: SomeProtocol {
func randomDouble() -> Double {
return Double(arc4random())
}
}

class SomeClass1: SomeProtocol {
func randomDouble() -> Double {
return Double(arc4random())
}
}

// 扩展协议,添加以及随机bool方法
extension SomeProtocol {
// 扩展协议,可以通过本身来实现这个方法
func randomBool() ->Bool {
return randomDouble() > 0.5
}
// 扩展协议,可以通过本身来实现这个属性
var name:String {
return "daisuke"
}

// 通过扩展协议,并本身实现相关属性或方法,让每个遵循协议的方法都能使用相关的属性和方法,也不用去重复实现。避免了冗余的代码,也变得简洁
}

let someClass1 = SomeClass1()
print(someClass1.name) // daisuke
提供默认实现

扩展的协议需要提供实现体,但是遵循协议的类型如果实现了扩展协议的属性或者方法,那么自定义的实现会覆盖扩展协议默认提供的实现。

注意:通过协议扩展为协议提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展的提供实现可以直接调用,而无需使用可选链调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protocol SomeProtocol {
var name:String { get }
}

// SomeProtocol1协议继承SomeProtocol
protocol SomeProtocol1: SomeProtocol {
var age:Int { get }
}

// 扩展SomeProtocol1协议,添加height属性
extension SomeProtocol1 {
var height:Double {
// 提供默认实现
return 173.3
}
}

class SomeClass: SomeProtocol1 {
var age: Int = 26
var name: String = "daisuke"

// 类实现了height属性,那么扩展协议的默认height实现就会被覆盖
var height: Double = 173.8
}

let someClass = SomeClass()
print(someClass.height) // 173.8
为协议扩展添加限制条件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protocol SomeProtocol {
var describe:String { get }
}

class SomeClass: SomeProtocol {
var describe: String {
return "SomeClass name is \(name)"
}
var name:String
init(name:String) {
self.name = name
}
}

// 扩展Collection协议,且容器中每一个元素都要遵循SomeProtocol协议。
extension Collection where Iterator.Element: SomeProtocol{
// 添加text属性,拼接每个item的describe,并返回
var text:String {
let itemText = self.map{$0.describe}
return "[" + itemText.joined(separator: ",") + "]"
}
}

// SomeClass遵循SomeProtocol
let oneClass = SomeClass(name: "one")
let twoClass = SomeClass(name: "two")
let threeClass = SomeClass(name: "three")

// 数组array1遵循Collection协议
let array1 = [oneClass, twoClass, threeClass]
print(array1.text)

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

参考: