• Swift-30-高级编程-类型扩展和协议扩展


    类型扩展

    Swift提供一个叫扩展(extension)的特性,该特性就是为这种情况设计的。扩展能让你给已有的类型添加功能,可以用来扩展结构体、枚举和类,比如:

    1. 添加计算属性
    2. 添加新的初始化方法
    3. 添加协议实现
    4. 添加新方法
    5. 添加嵌入类型(枚举和结构体)

    语法格式

    extension TypeName {}

    比如,为系统的Double对象添加一个mph方法,就可以用下列的代码实现

    extension Double {
        var mph: Double { return self }
    }
    
    • 1
    • 2
    • 3

    扩展系统类库

    // 给Double起个别名
    typealias Velocity = Double
    
    //添加两个计算属性
    extension Velocity {
        var kph: Velocity { return self * 1.60934 }
        var mph: Velocity { return self }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    扩展自定义类型

    本例子中会以自定义的Car结构体为例进行说明,如何扩展自定义的类型的。

    定义测试数据

    定义测试结构体

    • 自定义结构体
    // 自定义类型
    struct Car {
        let make: String
        let model: String
        let year: Int
        let color: String
        let nickname: String
        var gasLevel: Double { //可变的存储属性,附带了观察者实现
            willSet {
                precondition(newValue <= 1.0 && newValue >= 0, "New value must be between 0 and 1.")
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    定义测试协议

    在自定义协议添加三个计算属性

    protocol Vehicle {
        var topSpeed: Velocity { get } 
        var numberOfDoors: Int { get }
        var hasFlatbed: Bool { get }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    扩展实现

    用扩展方法实现协议

    下面的代码其实就是协议实现的另一种写法

    // 添加Vehicle协议实现
    extension Car: Vehicle {
        var topSpeed: Velocity { return 180 }
        var numberOfDoors: Int { return 4 }
        var hasFlatbed: Bool { return false }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    增强自定义类型功能

    • 添加初始化方法:前面提到过,如果结构体没有初始化方法的话,默认会自带一个成员初始化方法。如果想给结构体加一个新的初始化方法,同时又不想失去成员初始化方法,那就可以用扩展给结构体添加初始化方法。在扩展中为Car添加一个初始化方法,如下列代码清单所示。

    Car的成员初始化方法之所以能保留下来,是因为新初始化方法是在扩展中定义和实现的,这个模式很有用。如果用自定义初始化方法的方式会覆盖掉默认的初始化方法

    //扩展初始化方法
    extension Car {
        init(make: String, model: String, year: Int) {
            self.init(make: make,
                model: model,
                year: year,
                color: "Black",
                nickname: "N/A",
                gasLevel: 1.0)
        }
    } 
    
    //测试代码
    var c = Car(make: "Ford", model: "Fusion", year: 2013)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 添加嵌套类型和扩展:Swift扩展还可以给已有的类型添加嵌套类型,比如下例给Car添加一个枚举来协助汽车的分类。
    //添加嵌入类型
    extension Car {
        enum Kind { //添加的嵌套类型
            case coupe, sedan
        }
        var kind: Kind { //添加一个名为Kind的计算属性
            if numberOfDoors == 2 {
                return .coupe
            } else {
                return .sedan
            }
        }
    } 
    
    //测试代码
    var c = Car(make: "Ford", model: "Fusion", year: 2013)
    c.Kind
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 添加函数方法:用扩展给已有类型添加新的函数,下例中就给Car类型添加了两个新的方法。
    //添加函数
    extension Car {
        mutating func emptyGas(by amount: Double) {
            //precondition是一个框架方法
            precondition(amount <= 1 && amount > 0, "Amount to remove must be between 0 and 1.")
            gasLevel -= amount
        }
        
        mutating func fillGas() {
            gasLevel = 1.0
        }
    }
    
    //测试代码
    var c = Car(make: "Ford", model: "Fusion", year: 2013)
    c.emptyGas(by: 0.3)
    c.gasLevel
    c.fillGas()
    c.gasLevel
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    协议扩展

    OOP中的一个强大但也是一个缺陷功能就是继承,尤其过深的继承层次很容易让代码充满难以理解的类。Swift为设计可重用、可组合的类型带来了新机会:不用类和继承,而是用协议和泛型。即便使用值类型,协议也能解决OOP中继承能解决的问题。协议扩展(protocol extension)是使得这种设计成为可能的强大工具。语法结构如下:

    协议扩展:相当于一种变相的继承实现,对应的是objective-C中的概念。

    基础语法

    语法结构:protocol Exercise: CustomStringConvertible

    定义自定义的测试协议

    Exercise是一个自定义的协议,CustomStringConvertible是一个系统协议

    protocol Exercise: CustomStringConvertible {
        var name: String { get }
        var caloriesBurned: Double { get }
        var minutes: Double { get }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    扩展协议实现

    扩展协议添加方法

    在上面扩展的CustomStringConvertible协议中包含了一个名为description的函数,必须要实现,这段代码也可以写在上面的实现了,但为了演示协议的扩展,特意分出来一段代码实现,如下:

    extension Exercise {
        var description: String {
            return "Exercise(\(name), burned \(caloriesBurned) calories in \(minutes) minutes)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现自定义的协议

    自定义两个结构体,实现自定义的协议

    //定义结构体EllipticalWorkout,并扩展Exercise协议
    struct EllipticalWorkout: Exercise {
        let name = "Elliptical Workout"
        let title = "Workout using the Go Fast Elliptical Trainer 3000"
        let caloriesBurned: Double
        let minutes: Double
    }
    
    //Exercise(Elliptical Workout, burned 335.0 calories in 30.0 minutes)
    let ellipticalWorkout = EllipticalWorkout(caloriesBurned: 335, minutes: 30)
    
    //定义结构体TreadmillWorkout,并扩展Exercise协议
    struct TreadmillWorkout: Exercise {
        let name = "Treadmill Workout"
        let caloriesBurned: Double
        let minutes: Double
        let laps: Double
    }
    
    //可以二次扩展,这个方法会覆盖掉原来 Exercise 中的实现
    extension TreadmillWorkout {
        var description: String {
            return "Treadmill(\(caloriesBurned) calories and \(laps) laps in \(minutes) minutes)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    测试自定义的协议

    //Treadmill(350.0 calories and 10.5 laps in 25.0 minutes)
    let ellipticalWorkout = EllipticalWorkout(caloriesBurned: 335, minutes: 30)
    
    //Treadmill(350.0 calories and 10.5 laps in 25.0 minutes)
    let runningWorkout = TreadmillWorkout(caloriesBurned: 350, minutes: 25, laps: 10.5)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    扩展协议

    添加方法

    在原来的Exercise中添加新的方法。

    extension Exercise {
        var caloriesBurnedPerMinute: Double {
            return caloriesBurned / minutes
        }
    }
    //11.166666666666666
    let ellipticalWorkout = EllipticalWorkout(caloriesBurned: 335, minutes: 30)
    print(ellipticalWorkout.caloriesBurnedPerMinute)
    
    //14.0
    let runningWorkout = TreadmillWorkout(caloriesBurned: 350, minutes: 25, laps: 10.5)
    print(runningWorkout.caloriesBurnedPerMinute)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    添加扩展的限制条件

    用where子语句可以限制协议扩展的生效范围,比如Sequence就是一个系统的协议,用于实现集合迭代时的迭代对象的集合。如果要限制生效范围就可以用where语句来实现。

    比如下例中,扩展Sequence协议,但只对元素类型为Exercise的序列有效。

    extension Sequence where Iterator.Element == Exercise {
        //添加新的方法
        func totalCaloriesBurned() -> Double { 
            var total: Double = 0
            for exercise in self { //self值为 Sequence
                total += exercise.caloriesBurned
            }
            return total
        }
    }
    
    //创建测试数组,输出 ~~ 685.0
    let mondayWorkout: [Exercise] = [ellipticalWorkout, runningWorkout]
    print(mondayWorkout.totalCaloriesBurned())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    添加默认实现

    上述实现了给协议添加属性和方法功能,还可以利用协议扩展提供协议自身需求的默认实现。基实这段代码在之前出现了,为了突出功能,再详细解释下。通过这种实现那么所有符合Exercise协议的结构体默认就添加了此实现。

    extension Exercise {
        var description: String {
            return "Exercise(\(name), burned \(caloriesBurned) calories in \(minutes) minutes)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果在符合协议的类型中定义了同名的方法,则上述的默认实现就会被覆盖。

    //定义结构体TreadmillWorkout,并扩展Exercise协议
    struct TreadmillWorkout: Exercise {
        let name = "Treadmill Workout"
        let caloriesBurned: Double
        let minutes: Double
        let laps: Double
    }
    
    //覆盖了 extension Exercise 的实现
    extension TreadmillWorkout {
        var description: String {
            return "Treadmill(\(caloriesBurned) calories and \(laps) laps in \(minutes) minutes)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    振兴农村循环经济 和数链串起农业“生态链”
    Linux中断概述
    最佳实践:REST API 的 HTTP 请求参数
    和AI聊天:动态规划
    数据库常用的几大范式NF
    【电子元件】常用电子元器件的识别之二极管
    在 WPF 客户端实现 AOP 和接口缓存
    使用 BigDecimal 进行浮点数运算
    造芯:车企们的新赛场
    【Redis实现秒杀业务②】超卖问题的解决方案
  • 原文地址:https://blog.csdn.net/liudonglovehemin/article/details/138169518