Swift--协议

  • 协议概念
  • 协议定义和遵从
  • 协议方法
  • 协议属性
  • 面向协议编程

协议概念

image.png
  • 几何图形这种类在面向对象分析与设计方法学中称为抽象类,方法称为抽象方法。如果几何图形类中所有的方法都是抽象的,在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操作符判断类型是否遵从了某个协议。除了不能实例化,协议可以像其他类型一样使用

协议的继承
image.png

在协议中,一个协议还可以继承另一个协议,如上图,协议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()
协议的合成
image.png

如上图:类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。

image.png

如下图:通过扩展来实现的应用的类图。
类ViewController继承UIViewController,实现视图数据源协议UITableViewDataSource和表视图委托协议UITableViewDelegate都是在扩展中实现的(即在ViewController声明一个扩展,在扩展中实现协议UITableViewDataSource,再次在ViewController声明另一个扩展,实现协议UITableViewDelegate)。


image.png

看看以下的代码实现

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!)")
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343