在学习了利用访问控制来隐藏信息。隐藏信息是封装(encapsulation)的一种形式,可以在设计软件时达到其中一部分的变化不影响其他部分的目的。Swift还提供另一种形式的封装:协议。协议可以让你无须知道类型本身的信息,就能指定并利用类型的接口(interface),接口是类型提供的一组属性和方法。
Swift中的协议和ObjectiveC中的协议的概念是一样的,主要目的是在不修改类结构的前提下扩展类的功能。一个类可以实现多个协议。但在实现方式上Swift中的协议更像java接口的概念。
本节会以一个复杂的例子来学习协议的基本内容,我们会在这个例子中打印下列这样的一个表格。
协议基本实现
协议定义
语法结构:
protoclo proName{ properties..., function...}
用protocol关键字定义,可以定义属性和方法,首先定义一个名为TabularDataSource的协议。
swift
protocol TabularDataSource {
//定义协议属性和必须要实现的属性读取方法,get表示属性可读,也可以设置成{get set}
var numberOfRows: Int { get }
var numberOfColumns: Int { get }
//定义协议接口方法,所有实现此协议的类必须要实现以下两个方法
func label(forColumn column: Int) -> String //行标签
func itemFor(row: Int, column: Int) -> String //单元格数据
}
上述定义有一点需要注意,因为协议可由类和结构体实现,但结构体中的方法默认是不能改改其self值的,如果需要修改,则可将协议的 func label(){} 函数定义修改为 mutaling func label(){}。
协议实现
语法格式:
- 结构体和类实现语法:struct strName:ProtocolName,在结构体名称后面加上冒号+协议名称,比如struct Department: TabularDataSource;
- 类实现语法定义:类分有父类和无父类两种情况,写法一样,由swift自已来识别,比如
- 无父类:class className:protocolName1, protocolName2{};
- 有父类:class className:SuperClass, protocolName1, protocolName2{};
- 定义数据结构
swift
struct Person {
let name: String
let age: Int
let yearsOfExperience: Int
}
- 实现 TabularDataSource 协议
swift
//CustomStringConvertible是框架定义的一个协议,它里面只定义了一个名为description的属性,相当于toString方法
struct Department: TabularDataSource, CustomStringConvertible {
let name: String
var people = [Person]()
var description: String {
return "Department (\(name))"
}
init(name: String) {
self.name = name
}
mutating func add(_ person: Person) {
people.append(person)
}
//协议属性,必须实现
var numberOfRows: Int {
return people.count
}
var numberOfColumns: Int {
return 3
}
//协议接口方法,必须实现
func label(forColumn column: Int) -> String {
switch column {
case 0: return "Employee Name"
case 1: return "Age"
case 2: return "Years of Experience"
default: fatalError("Invalid column!")
}
}
//协议接口方法,必须实现
func itemFor(row: Int, column: Int) -> String {
let person = people[row]
switch column {
case 0: return person.name
case 1: return String(person.age)
case 2: return String(person.yearsOfExperience)
default: fatalError("Invalid column!")
}
}
}
协议做为参数使用
把协议做为参数传递,CustomStringConvertible是另一个系统实义的协议,注意代码写法,这里需要用到 &
关键字。
swift
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
print("Table: \(dataSource.description)")
// 创建表格头
var firstRow = "|"
// 记录每一列的宽度
var columnWidths = [Int]()
for i in 0 ..< dataSource.numberOfColumns {
let columnLabel = dataSource.label(forColumn: i)
let columnHeader = " \(columnLabel) |"
firstRow += columnHeader
columnWidths.append(columnLabel.count)
}
print(firstRow)
//拼装行数据
for i in 0 ..< dataSource.numberOfRows {
// 创建一个空符串
var out = "|"
// 拼接第一列数据到一行上
for j in 0 ..< dataSource.numberOfColumns {
let item = dataSource.itemFor(row: i, column: j)
let paddingNeeded = columnWidths[j] - item.count
let padding = repeatElement(" ", count: paddingNeeded).joined(separator: "")
out += " \(padding)\(item) |"
}
print(out)
}
}
协议测试
swift
var department = Department(name: "Engineering")
department.add(Person(name: "Joe", age: 30, yearsOfExperience: 6))
department.add(Person(name: "Karen", age: 40, yearsOfExperience: 18))
department.add(Person(name: "Fred", age: 50, yearsOfExperience: 20))
printTable(department)
高级用法
协议继承
语法结构:协议支持协议的继承,这点和java的接口比较相似,并且可以多继承
swift
protocol MyProtocol : ProtocolOne, ProtocolTwo{
}
这样上一节小的例子就可以定义成下列的结构,代码如下:
swift
//定义协议-继承模式, CustomStringConvertible为系统提供的一个标准协议
protocol TabularDataSource: CustomStringConvertible {
}
//协议实现
struct Department: TabularDataSource{
}
//协议做为参数使用
func printTable(_ dataSource: TabularDataSource){
}
协议组合
对于上面继承的模式实现有时不是太建议,因为灵活性差一些。因为这样架构的话会把两个不相关的功能耦合在了一起。所以一般很少用到协议继承。所以最好用组合的方式改成如下代码实现。
- 两个协议组合可以用&来连接,比如:TabularDataSource, CustomStringConvertible
- 多个协议组合可用用<>来连接,比如:<TabularDataSource, CustomStringConvertible, threeAble>
swift
//协议定义
protocol TabularDataSource{
}
//协议实现
struct Department: TabularDataSource, CustomStringConvertible{
}
//协议做为参数使用-组合模式
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
}