Wait the light to fall

Swift 扩展协议

焉知非鱼

协议的命名遵循 Swift 的标准库, 即协议名以 “Type”, “-able”, “-ible” 结尾

例如 SequenceType, GeneratorType, CustomStringConvertible, -type 定义行为, -able 定义元素怎样做某事。

protocol  ExerciseType: CustomStringConvertible {
    var name: String  { get }
    var caloriesBurned: Double { get }
    var minutes: Double { get }
}

// 结构体遵守 ErerciseType 协议, 但是也可以有自己的属性和方法, 这也是一种继承和封装
struct EllipticalTrainer: ExerciseType {
    let name = "Elliptical Machine"
    let caloriesBurned: Double
    let minutes: Double
}

struct Treadmill: ExerciseType {
    let name = "Treadmill"
    let caloriesBurned: Double
    let minutes: Double
    let distancesInMiles: Double
}


extension Treadmill {
    var description: String {
        return "Treadmill(\(caloriesBurned) calories and \(distancesInMiles) miles in \(minutes) minutes)"
    }
}


let ellipticalWorkout = EllipticalTrainer(caloriesBurned: 335, minutes: 30)
let runningWorkout    = Treadmill(caloriesBurned: 350, minutes: 25, distancesInMiles: 4.2)

扩展 ExerciseType #


有尖括号<>, 表明这是泛型函数, 而占位符类型(placeholder type)要求遵守 ExerciseType 协议


// 函数体中用了两个 ExerciseType 的属性来计算每分钟燃烧的卡路里

func caloriesPerMinutes<Exercise: ExerciseType>(exercise: Exercise) -> Double {
    return exercise.caloriesBurned / exercise.minutes
}
print(caloriesPerMinutes(ellipticalWorkout))
print(caloriesPerMinutes(runningWorkout))

caloriesBurnedPerMinute(_:) 没有一点错误, 但是每当你拥有一个 ExerciseType 的实例, 你必须记住 caloriesBurnedPerMinute(_:) 函数的存在。所以让每一个 ExerciseType 都有一个 caloriesBurnedPerMinute 属性更适合 —— 但是你不想把同样一份实现拷贝进 EllipticalTrainerTreadmill 结构体中(或者你新创建的 ExerciseType 中)

Swift 能扩展协议 #


扩展 ExerciseType 协议来添加一个属性

协议扩展能添加已经实现的属性和方法, 但是不能为协议添加新的必须要实现的属性和方法

很像你写泛型函数那样, 协议扩展的内部实现只能访问保证存在的其它属性的方法

添加到协议扩展中的属性和方法对于所有遵守该协议的类型来说都是可访问的。

extension ExerciseType {
    var caloriesBurnedPerMinutes: Double {
        return caloriesBurned / minutes
    }

    var description: String {
        return "Exercise(\(name), burned \(caloriesBurned) calories in \(minutes) minutes)"
    }
}

print(ellipticalWorkout.caloriesBurnedPerMinutes)
print(runningWorkout.caloriesBurnedPerMinutes)

协议扩展 – where 从句 #


扩展允许你为任意类型添加属性和方法, 不仅仅是你定义过的自定义类型

同样地, 协议扩展允许你给任何协议添加新的方法和属性, 然而, 就像我们上面提到的, 你为协议添加的属性和方法只能使用其它确定会存在的属性和方法。

使用 where 从句来为 SquenceTypes 协议扩展添加元素(Element)类型为指定类型的约束

extension SequenceType where Generator.Element == ExerciseType {
    func totalCaloriesBurned() -> Double {
        var total: Double = 0
        for exercise in self {
            total += exercise.caloriesBurned
        }
        return total
 }
}

协议扩展中的where 从句和泛型中的 where 从句的语法一样。你添加了一个 totalCaloriesBurned() 方法来计算序列所有运动(exercise)的卡路里总数。 在实现中, 你遍历 self 中的每个 exercise, 这是被允许的, 因为 self 就是某种 SequenceType

然后你访问每个元素中的 caloriesBurned 属性, 这是被允许的, 因为 where 从句约束这个序列的方法的元素类型是 ExerciseType

创建一个ExerciseTypes类型的数组, 数组遵守 SequenceType 协议。所以你可以调用你的新的 totalCaloriesBurned() 方法。

let mondayWorkout: [ExerciseType] = [ellipticalWorkout, runningWorkout]
print(mondayWorkout.totalCaloriesBurned())

这个数组能访问 totalCaloriesBurned() 方法, 因为它的元素类型是 [ExerciseType], 所以你得到结果为 685.0。 如果你创建了一个 [Int] 类型的数组, 那么 totalCaloriesBurned() 方法是访问不到的。它不会出现在 Xcode 的自动补全中, 即使你手动输入, 程序也会爆出编译错误。因为它满足不了 where 从句要求的数组的元素类型必须为 ExerciseType 的约束。

协议扩展的默认实现 #


print(ellipticalWorkout)
print(runningWorkout)

当一个协议为它的(某些或全部)属性和方法提供了默认的实现时, 遵守该协议的类型不一定要实现它们。但是它们能选择去实现它们如果默认实现不合适的话。

既然 Treadmill 自身实现了 description , 你应该看见默认的实现只出现在打印 ellpticalWorkout 上了.