枚举的许多面孔

对我来说这已经是忙碌的几个星期了,很高兴终于回到我可以适应例行程序的地步。 从单身派对到一个可怕的寒冷一周,再到哈德逊山谷度过一个愉快的假期,我再一次发现自己在布鲁克林,在计算机前编写代码(与之相对的另一种纸张和纸本一支钢笔)。

所以我们到了!

Swift有了对Objective-C的许多改进(对于那些久经考验的真正的Objective-C开发人员,我很容易受到侮辱),从面向协议的编程到功能更强大的编程方法,甚至是围绕泛型的更好的API。 Swift中的许多功能使其成为令人愉悦的开发体验。

我个人发现Swift中的枚举很棒。 它们一直是我最常用的语言功能之一,以至于我可能对它们有些执迷,这是一个错误。

但是,我将介绍一些我认为枚举可能非常强大的案例,特别是

  1. 澄清看似任意的值
  2. 状态的封装
  3. 错误包装

澄清看似随意的值

枚举最适合用来处理似乎晦涩难懂,难以记忆的价值并为其应用含义。 这主要是因为它们是可读值。 我们每天在语言中使用的单词具有很多含义,我们可以在有些令人困惑的值之上轻松地传递该含义。

让我们以CAGradientLayer为例。

CAGradientLayer具有两个属性,即startPointendPoint ,它们都是CGPoint.实例CGPoint.

苹果将​​这些文件记录如下

 绘制到图层的坐标空间中时,渐变的起点和终点。 起点对应于第一个渐变停止点,终点对应于... 

这是对这两个属性的不必要的冗长描述。 这两个点都代表渐变开始的x和y坐标,范围从0到endPointendPoint的默认值endPoint[0.5, 0] endPoint [0.5, 0][0.5, 1] endPoint [0.5, 1] 。 很难想象这意味着什么,但这是从上到下的垂直渐变。

如果要构建水平渐变层,则需要执行以下操作。

  func horizo​​ntalGradientLayer()-> CAGradientLayer { 
让渐变= CAGradientLayer()
gradient.startPoint = CGPoint(x:0,y:0.5)
gradient.endPoint = CGPoint(x:1,y:0.5)
返回梯度
}

即使现在只输入一次,我也必须考虑一下startPointendPoint可以正确定义水平渐变的不同可能性。 这种想法容易出错。

使用枚举定义渐变方向可以改善我们对渐变实际外观的可视化。

 枚举GradientDirection { 
垂直案例
案例水平
}

这不是功能最强大的枚举定义,但是已经很容易理解每​​个GradientDirection功能。 为了使此功能有用,我们需要为每个方向定义一个startPointendPoint

 枚举GradientDirection { 
垂直案例
案例水平
  var startPoint:CGPoint { 
得到{
切换自我{
大小写.vertical:
返回CGPoint(x:0.5,y:0)
大小写
返回CGPoint(x:0,y:0.5)
}
}
}
  var endPoint:CGPoint { 
得到{
切换自我{
大小写.vertical:
返回CGPoint(x:0.5,y:1)
大小写
返回CGPoint(x:1,y:0.5)
}
}
}
}

现在,当我要定义渐变层时,它更具可读性,因此可以理解。

  func horizo​​ntalGradientLayer()-> CAGradientLayer { 
让渐变= CAGradientLayer()
gradient.startPoint = GradientDirection.horizo​​ntal.startPoint
gradient.endPoint = GradientDirection.horizo​​ntal.endPoint
返回梯度
}

使用GradientDirection枚举,我们已经从平面上非常抽象的点概念转变为更为具体的方向概念。

更好的是,下一个调试渐变层代码的开发人员可以很容易地看到要实现的目标,因为枚举对其赋值使用了可理解的含义。

状态的封装

枚举与枚举可以帮助将含义和更深入的理解应用于抽象值的方式非常相似,枚举也可以帮助明确定义状态。 从AVPlayer的复杂实现到将对象简单加载到视图中,几乎所有情况都是如此。

定义良好且可读性强的表示状态的枚举可以极大地降低状态管理的复杂性,并减少未来开发人员的错误或回归的可能性。

让我们看一个简单的提要。

  FeedViewController类:UIViewController { 
var feed:饲料?
让collectionView:CollectionView
让feedId:字符串



func fetchFeed(){
保护self.loadingView == nil else {
返回
}
  self.displayLoadingView() 
NetworkService.shared.fetch(self.feedId){(提要,错误)在
如果错误!= nil {
self.displayError(error.localizedDescription)
}其他{
self.feed =提要
}
  self.removeLoadingView() 
}
}
}

这里有很多事情。

  • 检查屏幕上当前是否有加载视图
  • 告诉视图控制器在网络调用之前显示加载视图
  • 返回后检查网络呼叫的值
  • 如果有错误,显示错误
  • 否则,将self.feed设置为feed
  • 最后,从视图控制器中删除加载视图

这里可能出错的事情太多了,而且如果在这里必须处理任何更复杂的逻辑,那么很快就会失去控制。

再说一次,如果将来的开发人员在尝试处理错误时遇到了这段代码,他/她可能会重构它,而没有意识到他们可能造成的副作用。

在枚举FeedViewController时使用枚举封装FeedViewController的状态可以大大降低复杂度。

让我们定义这个枚举。

 枚举FeedState { 
加载情况(字符串)
装箱(进纸)
情况为空(错误?)
}

这是一个非常明确且易于理解的状态。 它可以是loading feedId, loadedfeed,或为empty,无论有无错误。 所有这些都非常容易表达,并且它们与提要之间的联系非常直观。

如预期的那样,这简化了fetchFeed.的逻辑fetchFeed.

  func fetchFeed(feedId:String){ 
切换self.state {
案例。加载:
返回
默认值:继续
}
  self.state = .loading(feedId) 
NetworkService.shared.fetch(feedId){(提要,错误)
状态=饲料!=无? .loaded(feed):.empty(错误)
}
}

现在,视图控制器只是在更新其状态属性,而不是根据网络的响应来处理视图的所有操作并执行不同的操作。

操作顺序,对错误的正确处理,所有这些都可以丢到窗口之外。 提要获取功能的内在复杂性已降低到单个属性的分配。

可以假定,在分配语句后将发生视图操作和错误处理,如下所示。

  var feedState:FeedState { 
didSet {
切换feedState {
案例。加载:
//显示加载视图
装箱。装箱(送纸)
//重新加载表格视图
大小写.empty(让错误)
//显示错误(如果存在)
}
}
}

显示哪些视图以及如何显示这些视图与不同状态紧密相关。 这使得跟踪变得更容易,调试起来更加容易,并且在以后的操作中也更加容易。

用枚举封装状态不仅使代码不太容易出现错误,而且还有助于准确定义供稿视图控制器的期望 。 阅读代码后,很清楚该视图可以做什么,以及它将如何对某些配置做出反应,这使得代码库非常友好。

错误包装

这里有一个共同的模式,就是可读性 。 可读性赋予代码含义,而含义又使代码成为未来的证明。 未来的证明是,维护代码库的开发人员将对代码应如何工作有更深入的了解,并能够更成功地维护代码。

考虑到这一点,错误常常会令人沮丧。 通常,当发生网络错误或代码库中发生意外情况时,字符串会随意传递,直到它们到达可以暴露给用户的位置为止。

这些错误字符串没有任何韵律或原因,除了字符串本身(或者可能是附加的代码)之外,与它们相关的意义很小。

您知道什么,枚举可以节省一天的另一个地方!

使用枚举将错误包装在整个应用程序中可以大大提高错误的可读性和可用性。

让我们以一个简单的网络错误为例。

  func fetchFeed(){ 
NetworkService.shared.fetch(feedId){(提要,错误)在
如果错误!= nil {
切换error.code {
案例500:
//显示自定义错误
案例404:
//显示自定义错误
}
}
}
}

幸运的是,我们确实从错误中获取了一个代码,这使我们能够以不同的方式处理不同的错误。 就灵活性而言,这很好,但是这些错误代码最终并没有多大意义。 他们真的只是数字。 有时甚至可能由于多种原因而获得相同的错误代码。

随着应用程序的扩展,如果这是所有错误的处理方式,那么错误代码将变得过度使用和繁琐,甚至开始失去其所具有的意义。

定义枚举可以缓解这些问题。 让我们定义一个尝试登录的枚举,因为向用户显示正确的错误通常很棘手。

 枚举LoginError:错误{ 
大小写无效密码
大小写无效
大小写无效
}

登录错误定义了三种情况。 密码无效,用户名无效或密码/用户名组合无效的情况。 这些都是未授权错误的子集,但是绕过基本错误并不得不在终止点解析出必要的信息可能会很丑陋。

相反,将错误包装在入口点的LoginError中可以大大简化在终端点处理错误的方式。

  func login(){ 
NetworkService.shared.login(credentials){(用户,错误)在
切换错误{
大小写.invalidPassword:
//将密码字段设为红色
大小写.invalidUsername:
//将用户名字段设为红色
大小写.invalidCredentials:
//显示重试提示
}
}
}

在这里,错误的种类与错误的结果之间有着非常明显的联系。 如果要针对特定​​错误的处理方式更改设计,则很容易踏入代码,查找适当的错误并调整代码流程。

并且由于将服务器错误转换为错误类型是在网络层进行的,因此服务器错误处理的任何更改都不会破坏视图层。 错误解析被遮盖。

使用枚举来包装错误实际上是一种很常见的做法,但是我发现回到它并重新评估其好处可以加强良好的做法,并有助于揭示枚举可能有用的其他地方。

在我们的应用程序中使用枚举的原因还有很多,但是今天我介绍的少数几个在编写代码时最有影响力。

如前所述,枚举实际上可以归结为一个简单的概念,那就是可读性 ,以及它如何将含义应用于我们的代码库。

我希望这是一本好书,并随时留下任何反馈!