swift 的协议以及不透明类型

1,260 阅读4分钟

swift 的协议以及不透明类型

概念

协议

协议定义了一个接口集合,用来声明某一特定任务或者功能的方法、属性,以及其他需要的东西。在swift语法中,类、结构体以及枚举都可以遵循协议,并实现协议接口中的方法。协议在面向接口、切片化、组件化等设计中起着至关重要的作用。由于在objective-c中没有多继承,因此,也会通过协议来实现面向接口化编程。

不透明类型

不透明类型的函数或方法的返回值,不是一个具体的类型,而是返回一个协议描述的类型。但与正常的返回协议描述的类型不同,不透明类型在实现时,返回的类型必须是唯一的,而正常返回协议类型的方法,则可以返回所有遵循此协议的对象。

使用方式说明

协议的声明场景,与普通类的方式十分相似。

protocol ShapeProctocol {
    func drawShape()
}
protocol PatterProctocol {
    var name: String? { get }
}
protocol CircularProctocol: ShapeProctocol, PatterProctocol {
    func getCircularCenter() -> CGPoint
}

通过上述代码可以看出,协议在声明时,可以定义方法和属性,而且一种协议,可以遵循一种或者多种协议。协议在面向接口编程中起着重要的作用,swift的开源项目swinject就是采用协议的方式,对各组件进行解耦,OC,swift中都没有严格意义上的抽象类,协议就充当着这种角色。

在iOS 开发中,协议-代理的模式也经常在一对一传值、函数方法回调中使用,相对于block,协议-代理的模式通过方法回调,方法书写可以更加集中。很多UIKit的框架都是使用这种方法,例如:UIScrollView,UITableview,值得注意的是,大多数代理属性被声明时,在ARC环境下,都是使用weak修饰,以防止内存泄漏,但是CAAnimation的delegate是使用strong修饰的。

/* The delegate of the animation. This object is retained for the
 * lifetime of the animation object. Defaults to nil. See below for the
 * supported delegate methods. */@property(nullable, strong) id <CAAnimationDelegate> delegate;

在讨论协议与不透明类型的时候,大体上,都是在讨论,两者作为返回值时有什么样的区别,或者说,两者在补充什么样的设计漏洞。使用协议作为返回值,可以使接口的抽象及通用型变得最大化,但同时,返回类型不确定,也就意味着很多依赖返回类型信息的操作就无法执行,举个🌰:

protocol TravelToolType { /* ... */ }
​
struct TheTaxiCar: TravelToolType {
    var carNumber: Int
}
​
struct TheSubWay: TravelToolType { /* ... */ }
​
public func favoriteTravelTool(withNumber travelNumber: Int) -> TravelToolType {
    if likeTaxi {
        return TheTaxiCar(carNumber: travelNumber)
    }
    return TheSubWay() 
}

这种实现方法,接口不对外暴漏实现方式,调用方只要清楚协议中定义了哪些方法,根据需要使用即可,类似于一种抽象工程的模式,非常好用。但是一旦我们的返回对象需要使用==来进行比较,情况就会发生变化:

  let weekTravel = favoriteTravelTool(withNumber: 100)
  let workTravel = favoriteTravelTool(withNumber: 100)
  print(weekTravel == workTravel) // Binary operator '==' cannot be applied to two 'TravelToolType' operands

产生这种错误可能由于很多原因,但是报错提示的直接原因是没有包含Equatable协议的声明,于是我们尝试加入Equatable协议的声明,结果如下:

protocol TravelToolType: Equatable { /* ... */ }
​
struct TheTaxiCar: TravelToolType {
    var carNumber: Int
}
​
struct TheSubWay: TravelToolType { /* ... */ }
// ---> Protocol 'TravelToolType' can only be used as a generic constraint because it has Self or associated type requirements
public func favoriteTravelTool(withNumber travelNumber: Int) -> TravelToolType { 
    if likeTaxi {
        return TheTaxiCar(carNumber: travelNumber)
    }
    return TheSubWay() 
}

新的问题来了,这里说一下我个人的理解解释,其中的隐喻可能不够精准,希望可以得到及时的补充,修改。由于返回协议是类似于一个隐蔽的盒子,里面装的是什么其实并不知道,而swift中==的使用,需要准确的知道盒子装的东西到底是猫罐头还是狗粮,也就是说 ,我们必须要知道返回的实体类型是什么。而现在并没有确定里面的类型,因此产生此类问题。

在关于不透明类型概念的介绍中,我们说到,不透明类型返回的某一个协议类型,怎么理解呢?同样是一个隐蔽的盒子,我们不知道里面装着什么,但是一旦加上了some关键字,就类似于给生产出来的盒子打上了生产标签,尽管我们仍然不知道盒子里到底是什么,但是我们知道这个生产的流水线只能生产出单一类型的某个产品,比如,只能生产出猫罐头或者狗粮。因此,使用不透明类型作为返回类型,就能够明确地表达所需要的API契约。修改后完整代码如下:

protocol TravelToolType: Equatable { /* ... */ }
​
struct TheTaxiCar: TravelToolType {
    var carNumber: Int
}
​
struct TheSubWay: TravelToolType { /* ... */ }
​
public func favoriteTravelTool(withNumber travelNumber: Int) -> some TravelToolType {
  return TheTaxiCar(carNumber: travelNumber)
}
​
let weekTravel = favoriteTravelTool(withNumber: 100)
let workTravel = favoriteTravelTool(withNumber: 100)
print(weekTravel == workTravel)
​
* true *\

参考文献

掘金:不透明类型和Swift中的some关键字

swiftGG:不透明类型

知乎: SwiftUI 带来的 Swift 5.1 新特性:不透明返回类型