美文网首页swift
swift中的协议和扩展

swift中的协议和扩展

作者: 枫叶1234 | 来源:发表于2019-10-30 14:43 被阅读0次

    1.Swift中的Protocol

    什么是Protocol?

    Protocol是Swift中的一种自定义类型,可以使用protocol定义某种约定,而不是某一种类型,一般用于表示某种类型的共性。

    Protocol 用法

    定义一个protocol

    protocol PersonProtocol {
        func getName()
        func getSex()
    }
    

    某个class、struct或者enum要遵守这种约定的话,需要实现约定的方法

    struct Person: PersonProtocol {
        func getName() {
             print("MelodyZhy")
        }
        func getSex() {
             print("boy")
        }
    }
    
    protocol中的约定方法,当方法中有参数时是不能有默认值的
    protocol中也可以定义属性,但必须明确指定该属性支持的操作:只读(get)或者是可读写(get set)
    protocol PersonProtocol {
        // 我们也可以在protocol中定义属性
        // ️必须明确指定该属性支持的操作:只读(get)或者是可读写(get set)
        var height: Int { get set }
        func getName()
        func getSex()
        // protocol中的约定方法,当方法中有参数时是不能有默认值的
        // ❌ Default argument not permitted in a protocol method
        // func getAge(age: Int = 18)
        func getAge(age: Int)
    }
    
    虽然height在protocol中是一个computed property,但在遵守该约定的类型中可以简单的定义成一个stored property
    当protocol中定义了一个只读属性,其实我们也可以在遵守该约定的类型中完成该属性的可读可写
    protocol PersonProtocol {
        var height: Int { get set }
        var weight: Int { get }
        func getName()
        func getSex()
        func getAge(age: Int)
    }
    
    struct Person: PersonProtocol {
        var height = 178
        var weight = 120
        func getName() {
             print("MelodyZhy")
        }
        func getSex() {
             print("boy")
        }
        func getAge(age: Int) {
            print("age = \(age)")
        }
    }
    
    var person = Person()
    person.height   // 178
    person.height = 180
    person.height  // 180
    
    person.weight // 120
    // 可以更改(但在以前版本的Swift中是不能直接这样更改的
    // 需要借助一个内部的stored property
    // 然后把这个属性设计成一个computed property实现 get 和 set 方法)
    person.weight = 130 // 130
    // 当我们把person从Person转换成PersonProtocol时 他就是只读的了
    // ❌Cannot assign to property: 'weight' is a get-only property
    (person as PersonProtocol).weight = 120
    
    如何定义可选的protocol属性或者方法?
    @objc protocol PersonProtocol {
        optional var height: Int { get set }
        optional var weight: Int { get }
        optional func getName()
        optional func getSex()
        optional func getAge(age: Int)
    }
    
    class Person: PersonProtocol {
        // 如果想提供可选的约定方法或者属性那么只能定义@objc的protocol
        // 并且这种约定只能class能遵守
    }
    
    protocol可以继承,当然struct、class、enum都可以同时遵守多个约定
    // 例如:
    protocol PersonProtocol {
        var height: Int { get set }
        var weight: Int { get }
        func getName()
        func getSex()
        func getAge(age: Int)
    }
    protocol Engineer: PersonProtocol {
        var good: Bool { get }
    }
    protocol Animal {
    }
    struct Person: Engineer, Animal {
        // 省略了该实现约定的方法和属性
    }
    

    2.protocol extension

    protocol extension 最关键的一点就是能在 protocol extension 方法中获取 protocol 的属性,因为Swift编译器知道任何一个遵守 protocol 的自定义类型,一定会定义这个 protocol 约定的各种属性,既然这样我们就可以在 protocol extension 中添加默认的实现了。这也是为什么会有 protocol oriented programming 这个概念,但这时候肯定会有人说我通过面对对象的编程方式也可以实现,但为什么要用遵守 protocol 的方法呢,这个要等到了解 extension 中的 type constraints 后解释...

    先看一个通过 protocol extension 添加默认实现的代码例子

    // 定义一个人属性的 protocol
    protocol PersonProperty {
        var height: Int { get } // cm
        var weight: Double { get } // kg
        // 判断体重是否合格的函数
        func isStandard() -> Bool
    }
    extension PersonProperty {
        // 给 protocol 添加默认的实现
        func isStandard() -> Bool {
            return self.weight == Double((height - 100)) * 0.9
        }
        // 给 protocol 添加默认属性
        var isPerfectHeight: Bool {
            return self.height == 178
        }
    }
    struct Person: PersonProperty {
        var height: Int
        var weight: Double
        // 如果自定义类型里面创建了遵守的 protocol 中的方法
        // 那么他将覆盖 protocol 中的方法
    //    func isStandard() -> Bool {
    //        return true
    //    }
    }
    // 创建遵守 PersonProperty 的自定义类型
    let p = Person(height: 178, weight: 61.5)
    // 那么 p 这个自定义类型 天生就有判断这个人身高体重是否合格的方法
    p.isStandard() // false
    // 同样天生具有判断是否是 Perfect Height 的属性
    p.isPerfectHeight // true
    

    protocol extension 中的 type constraints

    这相当于给 protocol extension 中的默认实现添加限定条件,写法如下

    // 运动因素的 protocol
    protocol SportsFactors {
        // 运动量
        var sportQuantity: Double { get }
    }
    
    // 下面这种写法就用到了 extension 中的 type constraints
    // 意思是 只有同时遵守了 SportsFactors 和 PersonProperty 时
    // 才使 PersonProperty 获得扩展 并提供带有 sportQuantity 属性的 isStandard 方法
    extension PersonProperty where Self: SportsFactors {
        func isStandard() -> Bool {
            // 随意写的算法 不要在意
            return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
        }
    }
    

    protocol oriented programming 的优点

    1、首先继承是 class 专有的,所以它不能用来扩展其他类型,但 protocol 是没有这种局限性的
    2、试想一下,上面的代码你用面对对象的编程方式的话可能你就需要多一个运动量的属性,同时也要修改 isStandard 函数,一切看起来特别自然,随着后续需求的更改可能会有更多因素影响是否是合格的体重,那么这时候你就会在不知不觉中将你代码的耦合度成倍提高,其实对于这个类来说,他完全不需要知道是否是合格体重的计算细节,所以我们完全可以把这些类型无关的细节从类型定义上移出去,用一个 protocol 封装好这些细节,然后让其成为这个类型的一种修饰,这就是POP的核心思想。
    3、当有多种因素制约是否是合格体重时,我们可以用多个 protocol 来对该类型进行修饰,每一种修饰的相关细节,我们都在对应的 protocol extension 中单独的封装起来,这样就大大降低了代码的耦合度,同时代码的可维护性也得到了相应的提高。swift标准库中大部分都是用这种思想构建的。**

    5.Protocol Oriented Programming 面向协议编程

    面向协议编程中,Protocol 实际上就是 DIP 中的抽象接口。通过之前的讲解,采用面向协议的方式进行编程,即是对依赖反转原则 DIP 的践行,在一定程度上降低代码的耦合性,避免耦合性过高带来的问题。下面通过一个具体实例简单讲解一下:
    首先是高层次结构的实现,创建EmmettBrown的类,然后声明了一个需求(travelInTime方法)。

    // 高层次实现 - EmmettBrown
    final class EmmettBrown {
        private let timeMachine: TimeTraveling
        init(timeMachine: TimeTraveling) {
            self.timeMachine = timeMachine
        }
        func travelInTime(time: TimeInterval) -> String {
            return timeMachine.travelInTime(time: time)
        }
    }
    

    采用 Protocol 定义抽象接口 travelInTime,低层次的实现将需要依赖这个接口。

    // 抽象接口 - 时光旅行
    protocol TimeTraveling {
        func travelInTime(time: TimeInterval) -> String
    }
    

    最后是低层次实现,创建DeLorean类,通过遵循TimeTraveling协议,完成TravelInTime抽象接口的具体实现。

    // 低层次实现 - DeLorean
    final class DeLorean: TimeTraveling {
        func travelInTime(time: TimeInterval) -> String {
            return "Used Flux Capacitor and travelled in time by: \(time)s"
        }
    }
    

    使用的时候只需要创建相关类即可调用其方法。

    // 使用方式
    let timeMachine = DeLorean()
    let mastermind = EmmettBrown(timeMachine: timeMachine)
    mastermind.travelInTime(time: -3600 * 8760)
    

    Delegate - 利用 Protocol 解耦

    委托(Delegate)是一种设计模式,表示将一个对象的部分功能转交给另一个对象。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。部分情况下,Delegate 比起自上而下的继承具有更松的耦合程度,有效的减少代码的复杂程度。

    那么 Deleagte 和 Protocol 之间是什么关系呢?在 Swift 中,Delegate 就是基于 Protocol 实现的,定义 Protocol 来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。

    Protocol 是 Swift 的语言特性之一,而 Delegate 是利用了 Protocol 来达到解耦的目的。

    Delegate 使用实例:
    //定义一个委托
    protocol CustomButtonDelegate: AnyObject{
        func CustomButtonDidClick()
    }
     
    class ACustomButton: UIView {
        ...
        weak var delegate: ButtonDelegate?
        func didClick() {
            delegate?.CustomButtonDidClick()
        }
    }
    
    // 遵循委托的类
    class ViewController: UIViewController, CustomButtonDelegate {
        let view = ACustomButton()
        override func viewDidLoad() {
            super.viewDidLoad()
            ...
            view.delegate = self
        }
        func CustomButtonDidClick() {
            print("Delegation works!")
        }
    }
    
    
    代码说明

    如前所述,Delegate 的原理其实很简单。ViewController会将 ACustomButtondelegate 设置为自己,同时自己遵循、实现了 CustomButtonDelegate 协议中的方法。这样在后者调用 didClick 方法的时候会调用 CustomButtonDidClick 方法,从而触发前者中对应的方法,从而打印出 Delegation works!

    循环引用

    我们注意到,在声明委托时,我们使用了 weak 关键字。目的是在于避免循环引用。ViewController拥有 view,而 view.delegate 又强引用了ViewController,如果不将其中一个强引用设置为弱引用,就会造成循环引用的问题。

    AnyObject

    定义委托时,我们让 protocol 继承自 AnyObject。这是由于,在 Swift 中,这表示这一个协议只能被应用于 class(而不是 struct 和 enum)。

    实际上,如果让 protocol 不继承自任何东西,那也是可以的,这样定义的 Delegate 就可以被应用于 class 以及 structenum。由于 Delegate 代表的是遵循了该协议的实例,所以当 Delegate 被应用于 class 时,它就是 Reference type,需要考虑循环引用的问题,因此就必须要用 weak 关键字。

    但是这样的问题在于,当 Delegate 被应用于structenum 时,它是 Value type,不需要考虑循环引用的问题,也不能被使用 weak关键字。所以当Delegate未限定只能用于 classXcode 就会对 weak 关键字报错:'weak' may only be applied to class and class-bound protocol types

    delegate只能用于类中

    定义协议的格式
    编写协议的格式:
    protocol 协议名字 : 基协议 {  //当然也可以不遵守基协议
        //方法的声明
    }
    
    例:定义一个买票的协议
    protocol buyTicketProtocol {
        func buyTicket() -> Void
    }
    
    Tips:
    如果一个协议继承了基协议NSObjectProtocol,那么遵守这个协议的类也必须要继承NSObject这个类
    
    遵守协议的格式

    一个类若要遵守一个协议,只需要在自己所继承的父类后面写上要遵守的协议名并以逗号","隔开,如果这个类无需继承,那么直接在冒号后面写上协议的名字就好

    遵守协议的格式:
    class Person : NSObject,SportProtocol{}
    
    例:定义一个会买票的黄牛类
    class Tout : buyTicketProtocol {  //无继承类遵守协议
        func buyTicket() {
            print("here's your ticket")
        }
    }
    
    Tips:
    Swift中的基协为NSObjectProtocol,这与OC中的基协议(NSObject)有些不同
    

    协议的继承

    • 上面有提到,当我们自定义一个协议的时候可以选择让这个协议继承自NSObjectProtocol,不单单如此,自定义的协议也可以遵守另外一个协议哦,基本格式如下:
    protocol showTicketNumberProtocol {  //展示票号
        func showTicketNumber() -> Void
    }
    protocol buyTicketProtocol : showTicketNumberProtocol  {  //买票
        func buyTicket() -> Void
    }
    
    • 如果一个类遵循了一个含有继承的协议,那么这个类就必须实现这个协议链中所有的必须实现的函数,否则编译报错
    class Tout : buyTicketProtocol  { 
        func buyTicket() {
            print("here's your ticket")
        }
        func showTicketNumber() {  //必须实现buyTicketProtocol所继承的"父"协议中的函数
            print("123456")
        }
    }
    
    Tips:
    上面提到的"如果一个协议继承了基协议NSObjectProtocol,那么遵守这个协议的类也必须要继承NSObject这个类"
    这是因为我们需要NSObject这个父类来帮我们实现NSObjectProtocol中定义的函数,否则编译器会以"没有实现NSObjectProtocol中的函数为由而报错
    
    协议中可选实现的函数

    为了保证Swift语言的严谨性,不建议在协议中定义可选实现的函数,不过不建议不代表不能嘛,我们可以利用OC特性来实现在Swift协议中定义可选实现函数

    • 创建带有OC特性的协议
    @objc  //表示一下代码含有OC特性
    protocol showTicketNumberProtocol {
        optional func showTicketNumber() -> Void  //optional修饰的函数为可选择实现(或不实现)的函数
    }
    
    • 遵守带有OC特性的协议
    class Tout : showTicketNumberProtocol  {
        //终于,下面这个函数可以不实现,并且不会报错了
        @objc func showTicketNumber() {  //由于showTicketNumberProtocol含有OC特性,于是这个协议中所有的函数在实现之前都要有@objc来修饰
            print("123456")
        }
    }
    

    相关文章

      网友评论

        本文标题:swift中的协议和扩展

        本文链接:https://www.haomeiwen.com/subject/tcbovctx.html