Swift中的protocol

前端之家收集整理的这篇文章主要介绍了Swift中的protocol前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

作为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 }表示只读

② 当protocol中的方法带有参数时,参数是不能有默认值的;如果要支持默认值,我们只能再定义两个方法


应用

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的数据成员了,

因为swift的编译器知道,任何一个遵从Flight的自定义类型 一定会定义Flight约定的各种属性

3)定义一个表示空客A380客机的类型:

struct A380: Flight { // 遵从Flight protocol
    var delay: Int    // 添加Flight约定的三个属性
    var normal: Int
    var flyHour: Int
}

4)此时,当定义了一个A380对象之后,就可以使用totalTrips获取总的飞行次数

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的区别

通过extension添加到protocol中的内容不算做protocol的约定

1)在A380为delayRate添加一个自定义实现 ,让它返回0.1:

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类型,要求它必须同时遵从OperationalLife

4)然后让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()

猜你在找的Swift相关文章