//swift标准库为我们提供了55种协议,他们的命名很有特点,基本是以“Type”、“able”、“Convertible”结尾,分别表示该协议“可以被当作XX类型”、“具备某种能力活着特性”、“能够进行改变活着变换”。因此在自定义协议时应该尽可能遵守苹果的命名规则,便于开发人员之间的高效合作。下面介绍一下常见的几种协议:
Equatable
// Equatable 是比较相关的协议,遵守协议表示实例能够用于相等的比价,需要重载==运算符
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 并且重载==运算符后直接比较两个学生的成绩是否相等了
let s1 = Student(math: 80, english: 60)
let s2 = Student(math: 70, english: 90)
let result = s1 == s2 //Equatable
//注意,由于重载==运算符是遵守Equatable协议后要求我们实现的,因此重载方法应该紧跟载遵守该协议的类型定义后,中间不能有其他的代码,否则就报错了
//Comparable是和比较相关的第二个协议,遵守该协议表示实例能够进行比较,需要重载<运算符。
struct Student: Comparable{
static func ==(lhs: Student, rhs: Student) -> Bool {
if ((lhs.math == rhs.math) && (lhs.english == rhs.english)){
return true
}else{
return false
}
}
var math:Int
var english:Int
}
//重载 < 运算符
func < (s1:Student,s2:Student)->Bool{
return ((s1.math != s2.math) || (s1.english != s2.english))
}
let s1 = Student(math: 70, english: 80)
let s2 = Student(math: 70, english: 90)
s1 < s2 //true
s1 == s2 //false
CustomStringConvertible
//CustomStringConvertible提供了一种用文本表示一个对象或者结构的方式,可以在任何遵守该协议的类型中自定义结构的文本,需要覆盖description属性。
struct Student: CustomStringConvertible {
var math: Int
var english :Int
var description: String {
return "Your math = \(math) , english = \(english)"
}
}
let s1 = Student(math: 80, english: 98)
print(s1) //Your math = 80 , english = 98
ExpressibleByArrayLiteral
//ExpressibleByArrayLiteral 提供了使用数组文本初始化的类型的能力,具体来说使用逗号分隔的值、实例、字面值列表
struct Person :ExpressibleByArrayLiteral {
var name : String = ""
var job :String = ""
typealias ArrayLiteralElement = String
init(arrayLiteral elements: String...) {
if elements.count == 2 {
name = elements[0]
job = elements[1]
}
}
}
let p1 : Person = ["jack","teacher"]
print(p1.name)
print(p1.job)
// 上面的代码用到了之前的关联类型,通过遵守ExpressibleByArrayLiteral,现在的Person 就可以使用数组直接创建实例了。类似的协议还有ExpressibleByDictionaryLiteral、ExpressibleByStringLiteral、ExpressibleByBooleanLiteralExpressibleByIntegerLiteral等等
为什么要使用协议
协议可以作为类型使用
协议作为一种类型是苹果在Swift中提出的,并且在官方文档中还为我们具体指出了可以将协议当做类型使用的场景:
1,在函数、方法或者初始化器里作为形式参数类型或者返回类型;
2,作为常量、变量或者属性的类型;
3,作为数组、字典或者其他存储器的元素的类型。
协议可以解决面向对象中一些棘手的问题
宠物类图
如图所示的类结构图中麻雀
在宠物类图中的位置显得比较尴尬,之所以尴尬是因为麻雀
作为一种鸟类,应该继承鸟
,但是如果继承了鸟
,就相当于默认了麻雀
是一种宠物
,这显然是不和逻辑的。解决此问题的一般方法如下:
宠物类图
乍一看好像解决了这样的问题,但是仔细想由于Swift只支持单继承,麻雀
没有继承鸟
类就无法体现麻雀
作为一种鸟拥有的特性和方法(比如飞翔),如果此时出现一个新的飞机
类,虽然飞机
和宠物
之间没有任何联系,但是飞机
和鸟
是由很多共同特性的(比如飞翔),这样的特性该如何体现呢?答案还是新建一个类成为动物
和飞机
的父类。面向对象就是这样一层一层的向上新建父类最终得到一个“超级父类”在OC和Swift中是NSObject
,尽管问题得到了解决,但是麻雀
与鸟
、飞机
与鸟
之间的共性并没有得到很好的体现。而协议的出现正是为了解决这类问题。
宠物类图
实际上宠物类图中包括动物
、鸟
、飞机
等类之间的关系就应该是如上图所示的继承关系。使用协议将“宠物”、“飞翔”等关系看作是一种特性,或者是从另一个维度描述这种类别,更重要的是使用协议并不会打破原有类别之间继承的父子关系。和飞翔相关的代码统一放在Flyable
中,需要“飞翔”这种能力遵守该协议;和宠物相关的代码统一放在PetType
中,需要成为宠物遵守该协议。这些协议灵活多变结合原有的面向对象类之间固有的继承关系,完美的描述了这个世界。这幅包含了协议的宠物类图是本人在学习中印象最深刻的,分享出来与大家共勉。
Swift中的协议更多的时候是在描述某种属性,是否应该将“宠物”设计成一个类或者是一个协议,应该根据当前项目的需求。如果你的世界没有麻雀
、飞机
,那么将“宠物”设计成一个类也是未尝不可甚至是非常合理的,这点需要我们仔细思考。
学习使用协议就不得不提到通过协议语法延伸出来的面向协议编程范式,苹果提出Swift是一门支持面向协议编程的语言,甚至提倡我们通过协议、结构体代替部分类的使用,可见协议这种语法以及面向协议编程思想在Swift中是多么的重要。在这里由于本人水平有限就不对此展开讨论,不过在学习中收集了几篇关于“使用协议编程”方面的文章,有兴趣的同学可以参考一下。
1,从 Swift 的面向协议编程说开去
2,我从55个Swift标准库协议中学到了什么?
3,Swift面向协议编程初探