作为swift中的一种自定义类型,和struct,class,enum不同,我们使用protocol来定义某种约定,而不是一个具体的类型。这种约定通常用于表示某些类型的共性
注:本篇文章学习自泊学(boxueio.com)
Protocol
定义
protocol Engine // 所有遵从Engine约定的类型都必须同时提供start和stop这两种方法。 { // -3. 属性 var cylinder: Int { get set } // 汽缸数 // ① var capacity: Double {get} // 排量 // -1. 方法 func start() func stop() // -2. 参数: // func getName(prefix: String = "")// ② 默认参数在protocol中是不被允许的 func getName(prefix: String) func getName() }
注释:
① 由于Engine并不是一个具体的类型,因此当我们在一个protocol中定义具体属性的时候,我们必须使用{ get set }这样的方法来明确指定该属性支持的操作。{ get set }表示可读写的,{ get }表示只读
应用
1)由于Engine只是一个约定,因此不能直接生成安全的对象
//let truck = Engine()
2)只能定义一个具体类型的struct,class,enum,让它们遵从Engine的约定,如:
class Truck: Engine {...}这和类继承的方式很像,但是 当冒号:后是一个protocol时,表示类型Truck遵从protocol Engine的约束。此时会报错,这是因为,虽然我们声明了Truck遵从Engine的约定,但是我们并没有真正实现start和stop这两个方法
3)Protocol的继承
除了定义属性和方法之外,protocol还可以继承的,用于表示约定A也是一个约定B,eg:
protocol TurboEngine: Engine { // var text: Int {get} func startTurbo() func stopTurbo() }let v8: TurboEngine表示v8不仅是一个TurboEngine,也是一个Engine
自定义类型遵从Protocol
1. 在自定义类型中实现Protocol中的方法
Protocol中的方法必须全部实现
class V8: TurboEngine{ // ******** 方法约定 ********* 必须实现 // ------ Engine methods ----- func start() { print("Engine start") } func stop() { print("Engine stop") } func getName(prefix: String) { print("\(prefix)-v8-engine") } func getName() { print("v8-engine") } // ------- TurboEngine methods ------ func startTurbo() { print("Turbo start") } func stopTurbo() { print("Turbo stop") } }
2. 在自定义类型中实现Protocol中的属性
Protocol中的属性也必须全部实现
1)Protocol中可读写的属性
class V8: TurboEngine{ // ******** 属性约定 ********* var cylinder = 8 }cylinder在protocol Engine里看起来像个computed property,但是在V8的实现里可以简单定义为stored property
let v8L40 = V8() v8L40.cylinder // get v8L40.cylinder = 18 // set.因此,一个stored property是满足protocol中get和set约定的
2)Protocol中只读的属性
class V8: TurboEngine{ // V8实现的时候: // 1. 可以定义一个常量 让capacity达到只读的效果; // let capacity = 4.0 // 不是必须的 // 2. 让capacity在V8里面变成一个变量 // var capacity = 4.0 // 3. 使用computed property来实现protocol里capacity的约定。当我们使用computed property的时候,通常需要定义一个内部的stored property,eg: private var innerCapacity = 4.0 // 然后定义一个computed property来实现capacity的约定 var capacity: Double { get { return self.innerCapacity }// 尽管在protocol里面,capacity只有get属性,但是在V8的实现里同样可以给它添加set方法 set { self.innerCapacity = newValue } } }这样 当一个变量的类型是V8的时候,刚才添加的capacity就是可写的,eg:
v8L40.capacity = 8.0但是如果把v8L40的类型转换成Engine或TurboEngine,capacity就会变成一个只读的
3. 遵从多个Protocol
在class的声明里使用 "," 将protocol分开 表示遵从多个约定
class V8: TurboEngine,Motor{...}
Protocol与Extension
1. 为Protocol添加额外的默认功能
protocol Flight{ // 航班信息 var delay: Int { get } // 航班晚点的次数 var normal: Int { get } // 航班正常的次数 var flyHour: Int { get }// 航班飞行的总时长 }1)拓展一个protocol 看似和拓展其他自定义类型没有太大的区别,都是使用extension关键字 + 要拓展的类型名字。
extension Flight { // 和定义protocol不同,我们可以在一个protocol extension中提供默认的实现,eg:在这里可以把totalTrips定义成一个computed property var totalTrips: Int { return delay + normal } func test ()->String{ return "test" } }2)尽管此时我们还没有定义任何遵从Flight的约定,但是已经可以在extension中使用Flight的数据成员了,
3)定义一个表示空客A380客机的类型:
struct A380: Flight { // 遵从Flight protocol var delay: Int // 添加Flight约定的三个属性 var normal: Int var flyHour: Int }
et a380 = A380(delay: 300,normal: 700,flyHour: 3 * 365 * 24) a380.totalTrips a380.test()playground:
1000 "test"
2. 为已有的方法提供默认的实现
protocol Flight{ // 航班信息 var delay: Int { get } // 航班晚点的次数 var normal: Int { get } // 航班正常的次数 var flyHour: Int { get }// 航班飞行的总时长 // eg2: func delayRate() -> Double }extension Flight { var totalTrips: Int { return delay + normal } func test ()->String{ return "test" } // eg2: func delayRate() -> Double { return Double(delay) / Double(totalTrips) } }a380.delayRate()
3. 1和2的区别
struct A380: Flight { // 遵从Flight protocol var delay: Int // 添加Flight约定的三个属性 var normal: Int var flyHour: Int // eg3: func delayRate() -> Double { return 0.1 } }a380.delayRate() (a380 as Flight).delayRate()
这时 无论flight1的类型是A380 还是Flight,delayRate的结果都会是0.1。原因:我们在A380中重新定义了Flight中约定的方法
2)注释掉在Flight中的delayRate方法:
此时再次调用:
@H_127_502@a380.delayRate() (a380 as Flight).delayRate()flight1的类型是A380时,delayRate的结果是0.1;flight1的类型是Flight时,delayRate的结果是0.3。
原因:此时delayRate不再是Flight约定的一部分了,在Flight extension中的delayRate只不过是为Flight protocol提供的一个方法1的添加的额外默认功能,既然delayRate不再是Flight约定的一部分了,那么swift编译器也不会认为A380中重定义的delayRate是在重新实现Flight中的约定,而只会把A380中的delayRate当成是普通方法,因此当我们把flight1的类型转换为Flight时,swift就会调用Flight的delayRate,事实上Flight的和A380中定义的delayRate没有任何关系。
4. 为默认实现限定其可用的条件
1)同时满足两个Protocol的类型,才可实现的方法
2)eg:
protocol OperationalLife{ var maxFlyHours: Int { get } }3)然后使用extension来为同时满足Flight和OperationalLife这两个protocol类型添加一个新的方法
extension Flight where Self: OperationalLife{ func isInService() -> Bool { return self.flyHour < maxFlyHours } }1️⃣使用关键字where来表示额外的遵从条件
2️⃣关键字Self用来表示遵从Flight类型,要求它必须同时遵从OperationalLife4)然后让A380遵从OperationalLife。
extension A380: OperationalLife { var maxFlyHours: Int { return 18 * 365 * 24 }// 假定服务年限为18年 }由于在extension里我们不能定义stored property,所以只能把maxFlyHours定义成一个computed property
5)此时a380就可以使用OperationalLife中定义的isInService方法了
a380.isInService()