- 协议概念
- 协议定义和遵从
- 协议方法
- 协议属性
- 面向协议编程
协议概念
- 几何图形这种类在面向对象分析与设计方法学中称为抽象类,方法称为抽象方法。如果几何图形类中所有的方法都是抽象的,在Swift和Objective-C中我们将这种类称为协议(protocol)。
- 实现抽象方法的过程在Swift和Objective-C中称为遵从协议或实现协议。
协议定义
protocol 协议名 {
// 协议内容, 包括:实例属性、静态属性、实例方法和静态方法
}
protocol Figure {
func onDraw() //定义抽象绘制几何图形
}
class Rectangle: Figure {
func onDraw() {
print("绘制矩形...")
}
}
class Circle: Figure {
func onDraw() {
print("绘制圆形...")
}
}
协议遵从
类型 类型名: 协议1, 协议2 {
//遵从协议内容
}
其中类型包括class、struct 和enum
class 类名: 父类, 协议1, 协议2 {
//遵从协议内容
}
协议方法
协议实例方法
- 协议可以要求其遵从者实现某些指定方法,包括实例方法和静态方法。
- 这些方法在协议中被定义,协议方法与普通方法类似,但不支持变长参数和默认值参数,也不需要大括号和方法体。
protocol Figure {
func onDraw() //定义抽象绘制几何图形
}
class Rectangle: Figure {
func onDraw() {
print("绘制矩形...")
}
}
class Circle: Figure {
func onDraw() {
print("绘制圆形...")
}
}
let rect: Figure = Rectangle()
rect.onDraw()
let circle: Figure = Circle()
circle.onDraw()
协议静态方法
- 协议中定义静态方法时前面要添加static关键字。
- 如果遵从者是结构体或枚举,关键字就是static。
- 如果遵从者是类,关键字可以使用class或static,使用class遵从者的子类中可以重写该静态方法,使用static遵从者的子类中不可以重写该静态方法,这个规则与子类继承父类的规则一样。
//协议静态方法
//协议中定义静态方法时前面要添加static关键字
protocol Account {
static func interestBy(amount: Double) -> Double
}
//遵从者是类,方法的关键字是class或static
//使用class遵从者的子类中可以重写该静态方法,
//使用static遵从者的子类中不可以重写该静态方法,
//这个规则与子类继承父类的规则一样
class ClassImp: Account {
class func interestBy(amount: Double) -> Double {
return 0.0668 * amount
}
}
//遵从者是结构体,方法的关键字是static
struct StructImp: Account {
static func interestBy(amount: Double) -> Double {
return 0.0668 * amount
}
}
//遵从者是枚举,方法的关键字是static
enum EnumImp: Account {
static func interestBy(amount: Double) -> Double {
return 0.0668 * amount
}
}
协议变异方法
在协议变异方法时,方法前面要添加mutating关键字。类、结构体和枚举类型都可以实现变异方法,类实现的变异方法时,前面不需要关键字mutating;而结构体和枚举实现变异方法时,前面需要关键字mutating。
1)变异方法一般在结构体和枚举中使用,类本身不需要变异方法,其实类本身定义的方法就是变异方法,只不过不需要加mutating关键字。
2)所谓变异方法就是能够在方法中修改它的属性,只要能够修改它的属性的方法就是变异方法。
3)由于在值类型中,就是在结构体和枚举所定义的类型里,它的方法是不能够修改它的属性的,除非给值类型的方法中添加mutating关键字使其变成变异方法才能修改其属性。
//协议变异方法
protocol Editable {
mutating func edit()
}
class ClassImp2: Editable {
var name = "ClassImp2"
func edit() {
print("编辑ClassImp2...")
self.name = "编辑ClassImp2..."
}
}
struct StructImp2: Editable {
var name = "StructImp"
mutating func edit() {
print("编辑StructImp2...")
self.name = "编辑StructImp2..."
}
}
enum EnumImp2: Editable {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
mutating func edit() {
print("编辑EnumImp2...")
self = .Friday
}
}
协议属性
协议实例属性
无论是存储属性还是计算属性,只要能满足协议属性的要求,就可以通过编译。甚至是协议中只规定了只读属性,而遵从者提供了对该属性的读写实现,这也是被允许的,因为遵从者满足了协议的只读属性需求。协议只规定了遵从者必须要做的事情,但没有规定不能做的事情。
protocol Person {
var firstName: String { get set }
var lastName: String { get set }
var fullName: String { get }
}
class Employee: Person {
var no: Int = 0
var job: String?
var salary: Double = 0
//协议属性firstName 和 协议属性firstNamelastName 具体的实现
var firstName: String = "Tony"
var lastName: String = "Guan"
var fullName: String {
get {
return self.firstName + "." + self.lastName
}
//协议属性规定只有get, 所以set方法可以实现也可不实现
set (newFullName) {
let separateName = newFullName.components(separatedBy: ".")
self.firstName = separateName[0]
self.lastName = separateName[1]
}
}
}
协议静态属性
- 协议中定义静态属性前面要添加static关键字。
- 如果遵从者是结构体或枚举,关键字就是static。
- 如果遵从者是类,关键字可以使用class或static, 使用class 遵从者的子类中可以重写该静态属性,使用static遵从者的子类中不可以重写该静态属性,这个规则与子类继承父类的规则一样。
protocol Account3 {
static var interestRate: Double {get}
static func interestBy(amount: Double) -> Double
}
class ClassImp3: Account3 {
static var interestRate: Double {
return 0.0668
}
class func interestBy(amount: Double) -> Double {
return ClassImp3.interestRate * amount
}
}
struct StructImp3: Account3 {
static var interestRate: Double = 0.0668
static func interestBy(amount: Double) -> Double {
return StructImp3.interestRate * amount
}
}
enum EnumImp3: Account3 {
static var interestRate: Double = 0.0668
static func interestBy(amount: Double) -> Double {
return EnumImp3.interestRate * amount
}
}
面向协议编程
在初学者看来,协议并没有什么用途(协议没有具体的实现代码,不能被实例化),但事实上协议非常重要,它的存在就是为了规范其他类型遵从它,实现它的方法和属性。
在OOAD(面向对象的分析和设计)中一个非常重要的原则是"面向接口编程",在Swift和Objective-C中称为"面向协议编程"。“面向协议编程” 使得面向对象类型(类、结构体和枚举)的定义与实现分离,协议作为数据类型暴露给使用者,使其不用关心具体的实现细节,从而提供代码的可扩展性和可复用性。
协议类型
- 协议类型可以作为函数、方法或构造函数中的参数类型或返回值类型;
- 协议类型可以作为常量、变量或属性的类型;
- 协议类型可以作为数组、字典和Set等集合的元素类型。
如下:
//面向协议编程
protocol Person2 {
var firstName: String { get set }
var lastName: String { get set }
var fullName: String { get }
func description() -> String
}
class Student2: Person2 {
var school: String
var firstName: String
var lastName: String
var fullName: String {
return self.firstName + "." + self.lastName
}
func description() -> String {
return "firstName: \(firstName) lastName: \(lastName) school: \(school)"
}
init(firstName: String, lastName: String, school: String) {
self.firstName = firstName
self.lastName = lastName
self.school = school
}
}
class Worker2: Person2 {
var factory: String
var firstName: String
var lastName: String
var fullName: String {
return self.firstName + "." + self.lastName
}
func description() -> String {
return "firstName: \(firstName) lastName: \(lastName) factory: \(factory)"
}
init(firstName: String, lastName: String, factory: String) {
self.firstName = firstName
self.lastName = lastName
self.factory = factory
}
}
let student1: Person2 = Student2(firstName: "Tom", lastName: "Guan", school: "清华大学")
let student2: Person2 = Student2(firstName: "Ben", lastName: "Guan", school: "北京大学")
let student3: Person2 = Student2(firstName: "Tony", lastName: "Guan", school: "香港大学")
let worker1: Person2 = Worker2(firstName: "Tom", lastName: "Zhao", factory: "钢厂")
let worker2: Person2 = Worker2(firstName: "Ben", lastName: "Zhao", factory: "电厂")
let people: [Person2] = [student1, student2, student3, worker1, worker2]
for item: Person2 in people {
if let student = item as? Student2 {
print("Student school: \(student.school)")
print("Student fullName: \(student.fullName)")
print("Student description: \(student.description())")
}else if let worker = item as? Worker2 {
print("Worker factory: \(worker.factory)")
print("Worker fullName: \(worker.fullName)")
print("Worker description:\(worker.description())")
}
}
注:协议作为类型使用,与其他类型没有区别,不仅可以使用as操作符进行类型转换,还可以使用is操作符判断类型是否遵从了某个协议。除了不能实例化,协议可以像其他类型一样使用
。
协议的继承
在协议中,一个协议还可以继承另一个协议,如上图,协议Student继承了协议Person, 遵从者Graduate 需要遵从Person和Student的所有协议中所规定的属性和方法。
//协议继承
protocol Person3 {
var firstName: String { get set }
var lastName: String { get set }
var fullName: String { get }
func description() -> String
}
protocol Student3: Person3 {
var school: String { get set }
}
class Graduate: Student3 {
var special: String
var firstName: String
var lastName: String
var school: String
var fullName: String {
return self.firstName + "." + self.lastName
}
func description() -> String {
return "firstName: \(firstName)\n lastName:\(lastName)\n School:\(school)\n Special:\(special)"
}
init(firstName: String, lastName: String, school: String, special: String) {
self.firstName = firstName
self.lastName = lastName
self.school = school
self.special = special
}
}
协议扩展
协议类型可以被扩展,这也是“面向协议编程” 非常重要的特征,这样我们就可以很灵活地将一些新功能添加到协议遵从者中。
//协议扩展
protocol Person4 {
var firstName: String { get set }
var lastName: String { get set }
var fullName: String { get }
}
//对协议Person4进行扩展,添加了方法printFullName
extension Person4 {
func printFullName() {
print("Print: \(fullName)")
}
}
class Employee4: Person4 {
var no: Int = 0
var job: String?
var salary: Double = 0
var firstName: String = "Tony"
var lastName: String = "Guan"
var fullName: String {
get {
return self.firstName + "." + self.lastName
}
set (newFullName) {
var name = newFullName.components(separatedBy: ".")
self.firstName = name[0]
self.lastName = name[1]
}
}
}
let emp = Employee4()
//协议本身不能有方法,但协议的扩展就可以有具体的方法,协议的遵从者可以调用协议扩展中的方法
emp.printFullName()
协议的合成
如上图:类Warship既实现了ship的协议,又实现了Weapon的协议,同时实现了2个协议,类是只可以继承一个,但是协议可以实现多个的。
//协议的合成
//定义轮船协议
protocol Ship {
var displacement: Double { get set }
}
//定义武器协议
protocol Weapon {
//火炮门数
var gunNumber : Int { get set }
}
//定义军舰类
class Warship: Ship, Weapon {
//排水量
var displacement = 1000_000.00
//火炮门数
var gunNumber = 10
}
//方法showWarResponse 的内部参数是个协议类型,
//这个协议规定了既能实现Ship协议,又能实现Weapon协议,这两个协议组成了一个新的协议
func showWarResponse(resource: Ship & Weapon) {
print("Ship \(resource.displacement) - Weapon \(resource.gunNumber)")
}
let ship = Warship()
showWarResponse(resource: ship)
扩展中遵从协议
语法:
extension 类型名:协议1,协议2 {
//协议内容
}
与协议中的扩展是不同的概念,协议中的扩展是有一个协议,原始类型是协议,是对原始的协议类型进行扩展。
而扩展中遵从协议 是有一个扩展,而这个扩展的原始类型可能是类、结构体、枚举等其他类型,同时这个扩展又可以去遵从某一个或多个特定的协议。
//扩展中遵从协议
protocol Editable4 {
mutating func edit()
}
struct Account4 {
var amount: Double = 10.0
var owner: String = ""
}
extension Account4: Editable4 {
mutating func edit() {
self.amount *= 100
self.owner = "Tony"
}
}
var account4 = Account4()
account4.edit()
print("\(account4.owner) -- \(account4.amount)")
面向协议编程示例:表示图中使用扩展协议
iOS开发中有一个表视图(UITableView),它可以在iOS设备上展示一个列表。显示表视图需要界面所在的视图控制器(ViewController)要求遵从表视图数据源协议UITableViewDataSource和表视图委托协议UITableViewDelegate。
如下图:视图控制器(ViewController)继承UIViewController,视图控制器遵从视图数据源协议UITableViewDataSource和表视图委托协议UITableViewDelegate。
如下图:通过扩展来实现的应用的类图。
类ViewController继承UIViewController,实现视图数据源协议UITableViewDataSource和表视图委托协议UITableViewDelegate都是在扩展中实现的(即在ViewController声明一个扩展,在扩展中实现协议UITableViewDataSource,再次在ViewController声明另一个扩展,实现协议UITableViewDelegate)。
看看以下的代码实现
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableView: UITableView!
var listTeams:[[String: String]]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//初始化表格
tableView = UITableView.init(frame: self.view.frame, style: .plain)
//表格的协议和代理
tableView.delegate = self
tableView.dataSource = self
//添加到视图上
self.view.addSubview(tableView!)
//获得plist文件到地址
let plistPath = Bundle.main.path(forResource: "person", ofType: "plist")
let dic = NSDictionary(contentsOfFile: plistPath!)
self.listTeams = dic?["student"] as? [[String: String]]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listTeams.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "CellIdentifier"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
let row = indexPath.row
let rowDict = self.listTeams[row] as [String: String]
cell.textLabel?.text = rowDict["name"] as String?
let imagePath = String(format: "%@.jpg", rowDict["image"]! as String)
cell.imageView?.image = UIImage(named: imagePath)
cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let rowDict = self.listTeams[row] as [String: String]
let name = rowDict["name"] as String?
print("人员姓名:\(name!)")
}
}
可以改成下面的样式:
class ViewController: UIViewController {
var tableView: UITableView!
var listTeams:[[String: String]]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//初始化表格
tableView = UITableView.init(frame: self.view.frame, style: .plain)
//表格的协议和代理
tableView.delegate = self
tableView.dataSource = self
//添加到视图上
self.view.addSubview(tableView!)
//获得plist文件到地址
let plistPath = Bundle.main.path(forResource: "person", ofType: "plist")
let dic = NSDictionary(contentsOfFile: plistPath!)
self.listTeams = dic?["student"] as? [[String: String]]
}
}
//扩展ViewController,实现 UITableViewDataSource协议
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listTeams.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "CellIdentifier"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
let row = indexPath.row
let rowDict = self.listTeams[row] as [String: String]
cell.textLabel?.text = rowDict["name"] as String?
let imagePath = String(format: "%@.jpg", rowDict["image"]! as String)
cell.imageView?.image = UIImage(named: imagePath)
cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
return cell
}
}
//扩展ViewController, 实现UITableViewDelegate协议
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let rowDict = self.listTeams[row] as [String: String]
let name = rowDict["name"] as String?
print("人员姓名:\(name!)")
}
}