Swift学习44:协议

张建 lol

前言

熟悉Objective-C语言的同学们肯定对协议都不陌生,在Swift中苹果将 protocol 这种语法发扬的更加深入和彻底。Swift中的 protocol 不仅能定义方法还能定义属性,配合 extension 扩展的使用还能提供一些方法的默认实现,而且不仅类可以遵循协议,现在的 枚举和结构体 也能遵循协议了。基于此本文从:

1、协议中定义属性和方法
2、协议的继承、组合、关联类型,
3、协议的扩展,
4、Swift标准库中常见的协议
5、为什么要使用协议

介绍

  • 协议 规定了用来实现某一特定功能所必须的属性和方法,这个属性指 实例属性或类型属性,而不用指定是 存储属性或计算属性

  • 类、结构体、枚举 都可以遵循协议,提供具体的实现 来完成协议定义的 属性和方法

协议的语法

  • 自定义类声明协议时,协议名放在类名的冒号 : 之后,多个协议用逗号分开

  • 若是一个类拥有父类,应该将父类名放在遵循的协议名之前,以逗号分隔。

定义协议:

1
2
3
4
// 单个协议
protocol SomeProtocol {
// dosomething
}

遵守协议:

1
2
3
4
// 多个协议
protocol SomeStruct: FirstProtocol,AnotherProtocol {
// structure definition
}

当一个类既有父类,有遵守其他协议时,将父类名写在遵守协议的前面

1
2
3
4
// 继承父类
class SomeClass: SomeSuperClass,FirstProtocol,AnotherProtocol {
// class definition
}

定义属性

协议可以要求遵循协议的类型提供特定名称和类型的 实例属性或类型属性。协议不具体说明属性是存储属性还是计算属性,它只具体要求 属性有特定的名称和类型

  • 协议同时要求一个 属性 必须 明确可读的/可读可写 的,类型声明后加上 { set get } 来表示属性是 可读可写 的;

  • 协议要求 属性变量类型 ,用 var 修饰,而不是let

1
2
3
4
protocol SomeProtocol {
var mustBeSettable: Int { get set } // 可读可写
var doesNotNeedToBeSettable: Int { get } // 可读
}
  • 协议定义 类型属性 时,前面添加 static 关键字。当类的实现使用 classstatic 关键字声明属性时,这个规则仍适用
1
2
3
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
  • 示例:
1
2
3
4
5
6
7
8
protocol Student {
// 定义一个可读可写的 name 属性
var name: String { get set }
// 定义一个可读的 birthPlace 属性
var birthPlace: String { get }
// 定义一个类属性 record
static var qualification: String {get}
}

和定义方法一样,我们只需要确定该属性具体是什么类型并且添加对应的关键字,不需要具体的实现,更不能为他们赋上初始值(类似于计算属性)。定义好属性之后,我们就可以利用属性来做点事情了。

1
2
3
4
5
6
struct Puple: Student {
static var qualification: String = "小学"
var name: String
var birthPlace: String = "北京"
}
var p1 = Puple(name: "小明", birthPlace: "上海")

定义一个 Puple 结构体遵循 Student 协议,该结构体中必须存在协议要求声明的三个属性 matrikelnummer、name、birthPlace, static 修饰的 类型属性 必须被有初始值或者存在 get、set 方法。对于普通的实例属性协议并不关心是计算型属性还是存储型属性。实例中的属性同样可以被修改:

1
2
var p1 = Puple(name: "小明", birthPlace: "上海")
Puple.qualification = "中学"

看到这里有的同学可能有些疑问,birthPlace、qualification 明明只有 get 方法为什么却可以修改赋值呢?其实协议中的“只读”属性修饰的是协议这种“类型”的实例,例如下面的例子:

1
2
var s1: Student = p1
s1.birthPlace = "广州"

虽然我们并不能像创建类的实例那样直接创建协议的实例,但是我们可以通过“赋值”得到一个协议的实例。将 p1 的值赋值给 Student 类型的变量 s1,修改 s1birthPlace 属性时编译器就会报错:birthPlace 是一个只读的属性,不能被修改。如果 Puple 中还存在 Student 没有的属性,那么在赋值过程中 s1 将不会存在这样的属性,尽管这样做的意义并不大,但是我们从中知道了协议中 get、set 的具体含义。

定义方法

  • 协议可以定义 实例方法和类方法,书写方式与正常的 实例方法和类方法相同。协议定义 类方法 时,添加 static 关键字,当类的实现使用 classstatic 关键字声明属性时,这个规则仍适用。方法的参数 不能有默认值(Swift认为默认值也是一种变相的实现),在遵守该协议的类型中具体实现方法的细节,通过类或实例调用
1
2
3
protocol SomeProtocol {
static func someTypeMethod()
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protocol Student {
//类方法
static func study()
//实例方法
func changeName()
}
struct CollageStudent: Student {
//类方法实现
static func study() {
}
//实例方法实现
func changeName() {
}
}
//方法的调用
CollageStudent.study()
var c1 = CollageStudent()
c1.changeName()

注意:当我们在结构体中的方法修改到属性的时候需要在方法前面加上关键字 mutating 表示该属性能够被修改(如果是类不需要添加 mutating 关键字),这样的方法叫做:异变方法,和 “在实例方法中修改值类型” 的处理是一样的。

1
2
3
4
5
6
7
8
9
10
protocol Student {
mutating func changeName()
}
struct CollageStudent: Student {
mutating func changeName() {
self.name = "小明"
}
}
var c1 = CollageStudent()
c1.changeName()

定义 mutating 异变方法

  • 在协议中,可变实例方法使用关键字 mutating 作前缀。在类中实现该方法时不需要写 mutating 关键字。mutating关键字仅供 结构体和枚举使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// mutating
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case off,on
mutating func toggle() {
switch self {
case .on:
self = .on
default:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
print(lightSwitch)

========
off

定义初始化

  • 协议允许定义 指定的初始化器,和一般初始化器一样,只是不用写大括号也就是初始化器的实体
1
2
3
protocol SomeProtocol {
init(someParameter: Int)
}
  • 你可以通过实现 指定的初始化器或便捷初始化器 来使遵循该协议的 满足协议的初始化器要求。

  • 在这两种情况下,你必须使用 required 关键字修饰初始化器的实现

1
2
3
4
5
6
7
8
9
protocol Pet {
init(name: String)
}
class Cat: Pet {
var name: String = "Cat"
required init(name: String) {
self.name = name
}
}

Cat 由于遵循了 Pet 协议,应该用 required 关键字修饰初始化器的具体实现。
如果一个 既继承了某个类,而且遵循了一个或多个协议,我们应该将父类放在最前面,然后依次用逗号排列。

1
2
class SomeClass: OneProtocol, TwoProtocol {
}

这是因为Swift中类的继承是单一的,但是类可以遵守多个协议,因此为了突出其单一父类的特殊性,我们应该将继承的父类放在最前面,将遵守的协议依次放在后面。

注意:在使用 final关键字修饰的类,不需要使用 required关键字 标记协议构造器的实现。

  • 如果 子类重写了父类的一个指定初始化器,并且 遵循协议 实现了初始化器要求,那么就要为这个初始化器的实现添加 requiredoverride 两个修饰符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 协议
protocol SomeProtocol {
// 初始化器
init()
}
// 父类
class SomeSuperClass {
// 初始化器
init() {
}
}
// 子类
class SomeClass: SomeSuperClass,SomeProtocol {
// 重写了父类的初始化器
init() {
}
required override init() {
// initializer ...
}
}
  • 多个协议重名方法调用冲突

由于在Swift中并没有规定不同的协议内方法不能重名(这样的规定也是不合理的)。因此我们在自定义多个协议中方法重名的情况是可能出现的,比如存在TextOne、TextTwo两个协议,定义如下:

1
2
3
4
5
6
protocol TextOne {
func text() -> Int
}
protocol TextTwo {
func text() -> String
}

这两个协议中的 text() 方法名相同返回值不同,如果存在一个类型 Person 同时遵守了 TextOneTextTwo,在Person实例调用方法的时候就会出现歧义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person: TextOne, TextTwo {
func text() -> Int {
return 10
}
func text() -> String {
return "hello"
}
}

let p1 = Person()
// 尝试调用返回值为Int的方法
let num = p1.text()
// 尝试调用返回值为String的方法
let string = p1.text()

上面的调用肯定是无法通过的,因为编译器无法知道同名 text() 方法到底是哪个协议中的方法,那么出现这种情况的根本原因在于调用哪个协议的 text() 不确定,因此我们需要指定调用特定协议的text() 方法,改进后的代码如下:

1
2
3
4
// 尝试调用返回值为Int的方法
let num = (p1 as TextOne).text()
// 尝试调用返回值为String的方法
let string = (p1 as TextTwo).text()

也可以理解为在进行调用前将 p1 常量进行 类型转换

将协议作为类型

  • 作为 函数、方法或者初始化器的形式参数类型或返回类型
  • 作为 常量、变量或者属性的类型
  • 作为 数组、字典或者其他存储器的元素的类型

首先,以下代码,通过继承基类实现的方式,如下:

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
class Shape{
// 计算属性
var area: Double{
get{
return 0
}
}
}
class Circle: Shape{
// 半径
var radius: Double

// 构造器
init(_ radius: Double) {
self.radius = radius
}

// 重写父类的属性
override var area: Double{
get{
return radius * radius * 3.14
}
}
}
class Rectangle: Shape{
var width, height: Double
init(_ width: Double, _ height: Double) {
self.width = width
self.height = height
}

override var area: Double{
get{
return width * height
}
}
}

var circle: Shape = Circle.init(10.0)
var rectangle: Shape = Rectangle.init(10.0, 20.0)

var shapes: [Shape] = [circle, rectangle]
for shape in shapes{
print(shape.area)
}

============
314.0
200.0

改为协议的方式实现,如下:

1
2
3
4
5
// 1. 将Shape改为protocol类型
// 2. 删除实现该协议的类的协议方法前缀override
protocol Shape {
var area: Double { get }
}

协议继承

官方文档说明:

协议可以继承一个或者多个其他协议并且可以在它继承的基础之上添加更多要求。协议继承的语法与类继承的语法相似,选择列出多个继承的协议,使用逗号分隔。

1
2
3
4
5
6
7
protocol OneProtocol {  
}
protocol TwoProtocol {
}
// 定义一个继承子OneProtocol 和 TwoProtocol协议的新的协议: ThreeProtocol
protocol ThreeProtocol: OneProtocol, TwoProtocol {
}

如上所示,任何遵守了 ThreeProtocol 协议的类型都应该同时实现 OneProtocolTwoProtocol 的要求 必须实现的方法或属性

类专属的协议

  • 通过添加 AnyObject 关键字到协议的继承列表,你就可以 限制协议只能被类类型采用(并且不是结构体或枚举)
1
2
3
protocol SomeClassOnlyProtocol: AnyObject,SomeProtocol {
// structure definition
}

协议组合

日常开发中要求一个类型同时遵守多个协议是很常见的,除了使用协议的继承外我们还可以使用形如 OneProtocol & TwoProtocol 的形式实现 协议聚合(组合) 复合多个协议到一个要求里。

1
2
3
4
5
// 协议聚合成临时的类型
typealias Three = TwoProtocol & OneProtocol
// 协议聚合成为参数的类型
func text(paramter: OneProtocol & TwoProtocol) {
}

例如:

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
// 协议组合
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named,Aged {
// 如果这个地方不定义属性会报错
// name属性是可读可写的,满足协议可读的要求
var name: String
var age: Int
}

let p = Person(name: "ZJ", age: 30)

// 满足了两个协议的要求,才能传递 参数
func wish(to: Named & Aged) {
print("name \(to.name) age \(to.age)")
}

wish(to: p)

======
name ZJ age 30

继承和聚合在使用上的区别:

善于思考的同学可以发现,要实现上面的 “paramter参数的类型是遵守OneProtocol 和 TwoProtoco” 的效果,完全可以使用协议的继承,新定义一个协议ThreeProtocol继承自OneProtocol 和TwoProtocol,然后指定paramter参数的类型是ThreeProtocol类型。那么这两种方法有何区别呢?

首先协议的继承是定义了一个全新的协议,我们是希望它能够“大展拳脚”得到普遍使用。而协议的聚合不一样,它并没有定义新的固定协议类型,相反,它只是定义一个临时的拥有所有聚合中协议要求组成的局部协议,很可能是“一次性需求”,使用协议的聚合保持了代码的简洁性、易读性,同时去除了定义不必要的新类型的繁琐,并且定义和使用的地方如此接近,见明知意,也被称为匿名协议聚合。但是使用了匿名协议聚合能够表达的信息就少了一些,所以需要开发者斟酌使用。

可选协议要求

  • 当协议中 某些方法或属性不需要遵守协议 的类型实现时,使用关键字 optional 来指明;
  • 协议和可选需求都必须用 @objc 属性标记;
  • @objc 协议只能被继承自 Objective-C 类或其他 @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
// 可选
@objc protocol CounterDataSource {
// 可选方法
@objc optional func increment(forCount count: Int) -> Int
// 可选属性
@objc optional var fixedIncrement: Int { get }
}
// 继承自NSObject类使用
class ThreeSource: NSObject, CounterDataSource {
// 可选属性
let fixedIncrement = 3
}
class TowardsZeroSource: NSObject, CounterDataSource {
// 可选方法
func increment(_ count: Int) -> Int {
if count > 0 {
return count
}else {
return 0
}
}
}

var threeSource = ThreeSource()
print(threeSource.fixedIncrement)
var towardsSource = TowardsZeroSource()
print(towardsSource.increment(5))

=======
3
5

检查协议一致性

  • 可以使用 isas 操作符来检查协议的一致性,并强制转换到特定的协议。如果一个实例符合协议,则 is 操作符返回 true,否则返回 false
1
2
3
4
5
6
struct Person: OneProtocol {
}
let p1 = Person()
if (p1 is OneProtocol){ // 可以理解为:p1 是一个遵守了OneProtocol协议类型的实例
print("yes")
}

如何让定义的协议只能被 遵守?:使用关键字 class,该关键字修饰之后表示协议只能被 遵守,如果有枚举或结构体尝试遵守会报错。

1
2
3
4
5
6
7
8
// 只能被类遵守的协议
protocol FourProtocol: class ,ThreeProtocol {
}
// 此处报错
struct Person: FourProtocol {
}
class Perple: FourProtocol {
}
  • 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
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 }
}

let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]

for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}

==============
Area is 12.5663708
Area is 243610.0
Something that doesn't have an area

关联类型

协议的关联类型指的是根据使用场景的变化,如果协议中某些属性存在 逻辑相同的而类型不同 的情况,可以使用关键字 associatedtype 来为这些属性的类型声明 关联类型

1
2
3
4
5
protocol WeightCalculable {
// 为weight 属性定义的类型别名
associatedtype WeightType
var weight: WeightType { get }
}

WeightCalculable 是一个“可称重”协议,weight 属性返回遵守该协议具体类型的实例的重量。这里我们使用 associatedtype 为该属性的类型定义了一个别名 WeightType,换言之在WeightCalculable 中并不关心 weight 的类型是 Int 还是 Double 或者是其他类型,他只是简单的告诉我们返回的类型是 WeightType,至于 WeightType 到底是什么类型由遵守该协议的类中自己去定义。那么这样做的好处是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
// 定义手机结构体
struct MobilePhone: WeightCalculable {
typealias WeightType = Double
var weight: WeightType
}
let iPhone7 = MobilePhone(weight: 0.138)
// 定义汽车结构体
struct Car: WeightCalculable {
typealias WeightType = Int
var weight: WeightType
}
let truck = Car(weight: 3000_000)

如上所示:MobilePhone、Car 类型都遵守了 WeightCalculable 协议,都能被称重,但是手机由于结构精密、体型小巧,小数点后面的数字对于称重来说是必不可少的,所以使用了 Double 类型,返回 0.138千克138克,但是对于汽车这样的庞然大物在称重时如果还计较小数点后面的数字就显得没有意义了,所以使用 Int 类型,表示 3000 千克也就是 3吨

从上面的例子可以很好的看出由于 MobilePhone、Car 称重时逻辑是一样的,但是对于 weight 属性的返回值要求不一样,如果仅仅因为返回值类型的不同定义两个类似的协议一个是 Int 类型另外一个是 Double 类型,这样做显然是重复的、不合适的。所以 associatedtype 在这种情况下就发挥出作用了,他让开发者在遵守协议时根据需求去定义返回值的类型,而不是在协议中写死。唯一要注意的是:一定要在遵守该协议的类型中使用 typealias 规定具体的类型。不然编译器就报错了。

协议的扩展

协议的扩展是协议中很重要的一部分内容,主要体现在以下两个方面:

  • 扩展协议的属性和方法

我们通过一个常见的例子说明一下:

1
2
3
4
5
protocol Score {
var math: Int { get set}
var english: Int {get set}
func mathPercent() -> Double
}

首先定义一个 Score 协议,里面有两个 Int 类型的属性 math、english 和一个计算数学所占分数的比例的方法 mathPercent

1
2
3
4
5
6
7
struct Puple: Score {
var math: Int
var english: Int
func mathPercent() -> Double {
return Double(math) / Double(math + english)
}
}

定义 Puple 遵守该协议,实现了必要的属性和方法。

1
2
let p1 = Puple(math: 90, english: 80)
s1.mathPercent()

通过上面的代码可以计算出 s1 中数学所占的比例,但是设想一下如果还有很多个类似 Puple 结构体的类型都需要遵守该协议,都需要默认实现 mathPercent 方法计算出自己的数学分数所占的比例,还是按照上面的写法代码量就很大而且很冗杂了。问题的关键在于:任何遵守 Score 协议类型的mathPercent 计算逻辑是不变的,而且需要默认实现。那么我们如何轻松的实现这样的效果呢,答案是:为 Score 添加方法的扩展。

1
2
3
4
5
extension Score {
func mathPercent() -> Double {
return Double(math) / Double(math + english)
}
}

mathPercent 的具体实现写在协议的扩展里面,就能为所有的遵守 Score 的类型提供mathPercent 默认的实现。

1
2
3
4
5
6
struct CollageStudent: Score {
var math: Int
var english: Int
}
let c1 = CollageStudent(math: 80, english: 80)
c1.mathPercent()

如此就能起到 不实现mathPercent方法也能计算出数学所占分数的比例 的效果了。此语法在Swift中有一个专业术语叫做:default implementation默认实现。包括 计算属性和方法 的默认实现,但是 不支持存储属性,如果遵循类型给这个协议的要求提供了它自己的实现,那么它就会替代扩展中提供的默认实现。
通过这样的语法,我们不仅能为自定义的协议提供扩展,还能为系统提供的协议添加扩展,例如,为 CustomStringConvertible 添加一个计算属性默认实现的扩展:

1
2
3
4
5
extension CustomStringConvertible {
var customDescription: String {
return "ZJ" + description
}
}
  • 为存在的类型添加协议遵守

官方文档说明:

扩展一个已经存在的类型来采纳和遵循一个新的协议,无需访问现有类型的源代码。扩展可以添加新的属性、方法和下标到已经存在的类型,并且因此允许你添加协议需要的任何需要。

简单的来说我们可以对存在的类型(尤其是系统的类型)添加协议遵守。尽管这更像是对“类型的扩展”,但是官方文档将这部分放在了协议的扩展中。

1
2
3
4
extension Double : CustomStringConvertible {
/// A textual representation of the value.
public var description: String { get }
}

上面的代码就是 Swift 标准库中对于 Double 类型添加的一个协议遵守。除了添加系统协议的遵守,我们还可以添加自定义的协议的遵守,其方法都是一样的,这里就不太赘述了。

  • 总结

通过 协议的扩展 提供协议中某些属性和方法的默认实现,将公共的代码和属性统一起来极大的增加了代码的复用,同时也增加了协议的灵活性和使用范围,这样的协议不仅仅是一系列接口的规范,还能提供相应的逻辑,是面向协议编程的基础。

Swift标准库中常见的协议

学习完协议的基础语法,我们大致熟悉一下Swift标准库中提供的协议。

Swift标准库为我们提供了55种协议,他们的命名很有特点,基本是以”Type”、“able”、“Convertible” 结尾,分别表示该协议“可以被当做XX类型”、“具备某种能力或特性”、“能够进行改变或变换”。因此在自定义协议时应该尽可能遵守苹果的命名规则,便于开发人员之间的高效合作。下面介绍一下常见的几种协议:

  • Equatable

Equatable 是和比较相关的协议,遵守该协议表示实例能够用于相等的比较,需要重载 == 运算符。

1
2
3
4
5
6
7
8
struct Student: Equatable {
var math: Int
var english: Int
}
// 重载 == 运算符
func == (s1: Student, s2: Student) -> Bool {
return s1.math == s2.math && s1.english == s2.english
}

Student遵守 Equatable 并且重载了 == 运算符后就能直接比较两个学生的成绩是否相等了。

1
2
3
let s1 = Student(math: 80, english: 60)
let s2 = Student(math: 70, english: 90)
s1 == s2 //false

值得注意的是,由于重载 == 运算符是遵守 Equatable 协议后要求我们实现的,因此重载方法应该紧跟在遵守该协议的类型定义后,中间不能有其他的代码,否则就报错了。

  • Comparable

Comparable 是和比较相关的第二个协议,遵守该协议表示实例能够进行比较,需要重载 < 运算符。

1
2
3
4
5
6
7
8
9
10
11
struct Student: Comparable{
var math: Int
var english: Int
}
// 重载 < 运算符
func < (s1: Student, s2: Student) -> Bool {
return (s1.math + s1.english) < (s2.math + s2.english)
}
let s1 = Student(math: 80, english: 60)
let s2 = Student(math: 70, english: 90)
s1 < s2 //true
  • CustomStringConvertible

CustomStringConvertible 提供了一种用文本表示一个对象或者结构的方式,可以在任何遵守该协议的类型中自定义表示结构的文本,需要覆盖 description 属性。

1
2
3
4
5
6
7
8
9
struct Student: CustomStringConvertible{
var math: Int
var english: Int
var description: String {
return "Your math:" + String(math) + ", english:" + String(english)
}
}
let s1 = Student(math: 80, english: 60)
print(s1) // Your math:80, english:60
  • ExpressibleByArrayLiteral

ExpressibleByArrayLiteral 提供了使用数组文本初始化的类型的能力,具体来说使用逗号分隔的值、实例、字面值列表,方括号以创建数组文本。遵守该协议需要实现 init(arrayLiteral elements: Person.Element...) 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person: ExpressibleByArrayLiteral {
var name: String = ""
var job: String = ""
typealias Element = String
init(arrayLiteral elements: Person.Element...) {
if elements.count == 2 {
name = elements[0]
job = elements[1]
}
}
}
let p1: Person = ["jack", "teacher"]
print(p1.name) //jack
print(p1.job) //teacher

上面的代码用到了之前关联类型,通过遵守 ExpressibleByArrayLiteral,现在的 Person 就可以使用数组直接创建实例了。

类似的协议还有:
ExpressibleByDictionaryLiteral、ExpressibleByStringLiteral、ExpressibleByBooleanLiteral 、ExpressibleByIntegerLiteral 等等,相信大家通过名称就能大概猜出具体作用,由于篇幅有限这里就不再赘述了。

为什么要使用协议

  • 协议可以作为类型使用

协议作为一种类型是苹果在Swift中提出的,并且在官方文档中还为我们具体指出了可以将协议当做类型使用的场景:

1,在函数、方法或者初始化器里作为形式参数类型或者返回类型;
2,作为常量、变量或者属性的类型;
3,作为数组、字典或者其他存储器的元素的类型。

  • 协议可以解决面向对象中一些棘手的问题
  • Post title:Swift学习44:协议
  • Post author:张建
  • Create time:2023-03-05 17:04:54
  • Post link:https://redefine.ohevan.com/2023/03/05/Swift课程/Swift学习44:协议/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.