内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

Swift的可选型枚举

2018-06-08 19:56 出处:清屏网 人气: 评论(0

去年我写了一篇关于 在类中添加普通可选型属性使扩展功能变得更简单 的文章,但是从长远来看会对代码库造成一定的损害,本文接上一篇内容。

假设你正在设计 App 中的认证流程,而且知道这个流程不是简单的线性执行代码,所以想写一些测试代码。

首先列举出流程中的每一步:

enum AuthFlowStep {
    case collectUsernameAndPassword
    case findFriends
    case uploadAvatar
}

然后,将所有复杂的逻辑放入到一个函数中,该函数接收当前步骤和当前状态,返回流程中的下一个步骤。

func stepAfter(_ currentStep: AuthFlowStep, context: UserState) -> AuthFlowStep

这应该很容易测试,到目前为止一切正常。

但是,在认真思考逻辑之后,你会发现有时候不能返回 AuthFlowStep 。一旦用户提交了所有认证需要的数据,你就需要想个办法表示流程已经结束了。在这个函数中,你需要返回一个特殊值。所以要怎么做呢?很简单,把返回类型改为可选值即可:

func stepAfter(_ currentStep: AuthFlowStep, context: UserState) -> AuthFlowStep?

这个方法可以解决问题,你可以在 coordinator 中调用这个函数,继续实现你的功能:

func finished(flowStep: AuthFlowStep, state: UserState, from vc: SomeViewController) {
	let nextState = stepAfter(flowStep, context: state)

由于 nextState 是可选值,所以最直接的想法就是用 guard 方法把它变成非可选值。

guard let nextState = stepAfter(flowStep, context: state) else {
  self.parentCoordinator.authFlowFinished(on: self)
}
  switch nextState {
  case .collectUsernameAndPassword:
		//build and present next view controller

但是我总觉得这里的写法有点问题。阅读 Olivier 的模式匹配指南 之后,我发现可以在 switch 语句中同时处理可选值和枚举值:

func finished(flowStep: AuthFlowStep, state: UserState, from viewController: SomeViewController) {
	let nextState = stepAfter(flowStep, context: state) // Optional<AuthFlowStep>
	switch nextState {
	case nil:
		self.parentCoordinator.authFlowFinished(on: self)
	case .collectUsernameAndPassword?:
		//build and present next view controller

代码里的那个问号可以匹配枚举的可选值。这种写法确实更好,但还是有点不对劲。既然我已经用了 switch ,为什么还要做解包操作? nil 在这个上下文中又代表着什么?

如果你认真读过这篇文章的标题,或许已经猜到了我下面要做什么。我们先来看看可选值的定义。在底层代码中,它和 AuthFlowState 一样是个枚举:

enum Optional<Wrapped> {
	case some(Wrapped)
	case none
}

把枚举转换成可选类型时,实际上只是向枚举中添加了一个新值。既然我们能直接控制 AuthFlowStep ,直接给它添加一个新值就能实现同样的效果。

enum AuthFlowStep {
    case collectUsernameAndPassword
    case findFriends
    case uploadAvatar
    case finished
}

现在我们可以从函数返回值类型中删掉 ? 了。

func stepAfter(_ currentStep: AuthFlowStep, context: UserState) -> AuthFlowStep

我们的 switch 语句现在可以直接处理所有步骤,不需要对 nil 做特殊处理。

为什么这样更好?有几个原因:

首先,现在 nil 对应的情况有了具体的名字。以前,使用该函数的用户可能不清楚函数返回 nil 意味着什么。他们要么去阅读文档求助,要么直接阅读函数代码,分析什么时候会返回 nil

第二,简单才是王道,不需要先用 guard 解包再用 switch 判断,也不需要用 swtich 语句处理两层枚举,一层枚举更容易处理。

最后,代码更加健壮。 return nil 应该留给真正异常情况。下一个开发者可能需要在某些特殊情况发生时退出函数,他想都没想就写了个 return nil 。这时 nil 就具备了两种含义,你的代码无法正确处理。

当你把特殊情况添加到枚举中时,需要想好到底使用什么名字。你有很多选择,挑一个最合适的: .unknow.none.finished.initial.notFound.default.nothing.unspecified 等等(需要注意,如果你有一个 case 匹配的是 .none ,并且匹配的值是可选值,那么 Option.noneYourEnum.none 都会引起歧义,所以不要在匹配可选值的时候使用 .none 去表示你自己的状态)。

这篇文章介绍的是流程状态,但我觉得这种模式也同样适用其他情况 — 如果你想把一个枚举改成可选值,最好先停下来想一想,是否可以给枚举加一个新值来表示特殊情况。

感谢 Bryan Irace 提出的反馈和示例代码。


分享给小伙伴们:
本文标签: 可选型枚举Swift

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号