一、前言

  • Optional 是 Objective-C 没有的数据类型,是苹果引入到 Swift 语言中的全新类型,它的特点就和它的名字一样:可以有值,也可以没有值,当它没有值时,就是 nil。
  • 可选选项 Optional 可以说是 Swift 最重要的特性之一,也是它区别于 Objective-C 等语言的关键,通过被强制处理可能为 nil 的情况,我们倾向于编写更可预测和更少出错的代码。
  • 然而,有些时候可选值可能会致我们于尴尬的境地,尤其是作为开发者了解(甚至是有些猜测的成分在),有的特定变量始终是非空(non-nil)的,即使它是一个可选类型。例如,在一个视图控制器中处理视图的时候:
class TableViewController: UIViewController {var tableView: UITableView?override func viewDidLoad() {super.viewDidLoad()tableView = UITableView(frame: view.bounds)view.addSubview(tableView!)}func viewModelDidUpdate(_ viewModel: ViewModel) {tableView?.reloadData()}
}
  • 这也是对于很多 Swift 程序员争论比较激烈的地方,程度不亚于讨论 tabs 和 spaces 的用法。有的人会说:既然它是一个可选值,就应该时刻使用 if let 或者 guard let 的方式进行解包。
  • 而另一些人则会持完全不同的观点,说:既然知道这个变量在使用的时候不会为 nil,使用强制解包( ! )多好。崩溃也要比让程序处于一个未知状态要好。
  • 本质上来讲,这里讨论的是要不要采用防御性编程 defensive programming 的问题。我们是试图让程序从一个未知状态恢复还是简单的放弃?然后让它崩溃掉吗?
  • 如果非得对这个问题给出一个答案的话,我更倾向于后者,未知状态真的很难追踪 bug,会导致执行很多不想执行的逻辑,采用防御性编程就会使得代码很难追踪,出现问题很难追踪。
  • 但是,我不太喜欢给出一个二选一的答案。相反,可以寻找一些技术手法,用更精妙的方式的解决上面提到的问题。

二、Optional 真的可选的吗?

  • 那些可选类型的,但是被代码逻辑真实需要的变量和属性,实际上是架构瑕疵的一个体现。如果在某些地方确实需要它,但是它又不在,就会使得你的代码逻辑处于未知状态,那么它就不应该是可选类型的。
  • 当然,在某些特定场景下,可选值确实很难避免(尤其是和特定的系统 API 交互的时候),那对于大部分这种情况,我们有一些技术来处理从而避免可选值。

三、lazy 要比非可选的可选值更好

  • 某些属性的值需要在其父类创建之后再生成(比如视图控制器中的那些视图,应该在 loadView() 或者 viewDidLoad() 方法中被创建),对于这种属性要避免其可选类型的方法就是使用 lazy 属性。一个 lazy 属性是可以是非可选类型的,同时也不在其父类的初始化方法里被需要,它会在其第一次被获取的时候创建出来。
  • 来修改一下上面的代码,使用 lazy 来改造 tableView 属性:
class TableViewController: UIViewController {lazy var tableView = UITableView()override func viewDidLoad() {super.viewDidLoad()tableView.frame = view.boundsview.addSubview(tableView)}func viewModelDidUpdate(_ viewModel: ViewModel) {tableView.reloadData()}
}
  • 这样,没有可选项,也没有未定义的状态。

四、适当的依赖关系管理要比非可选的可选值要好

  • 可选值类型另外一种常用的场景就是用来打破循环依赖(circular dependencies)。有的时候,就会陷入 A 依赖 B,B 又依赖 A 的情况,如下:
class UserManager {private weak var commentManager: CommentManager?func userDidPostComment(_ comment: Comment) {user.totalNumberOfComments += 1}func logOutCurrentUser() {user.logOut()commentManager?.clearCache()}
}class CommentManager {private weak var userManager: UserManager?func composer(_ composer: CommentComposerdidPostComment comment: Comment) {userManager?.userDidPostComment(comment)handle(comment)}func clearCache() {cache.clear()}
}
  • 从上面的代码,可以看到,UserManager 和 CommentManager 之间有一个循环依赖的问题,它们二者都没法假设自己拥有对方,但是它们都在各自的代码逻辑里依赖彼此,这里就很容易产生 bug。
  • 那要解决上面的问题,我们创建一个 CommentComposer 来做一个协调者,负责通知 UserManager 和 CommentManager 二人一个评论产生:
class CommentComposer {private let commentManager: CommentManagerprivate let userManager: UserManagerprivate lazy var textView = UITextView()init(commentManager: CommentManager,userManager: UserManager) {self.commentManager = commentManagerself.userManager = userManager}func postComment() {let comment = Comment(text: textView.text)commentManager.handle(comment)userManager.userDidPostComment(comment)}
}
  • 通过这种形式,UserManager 可以强持有 CommentManager 也不产生任何依赖循环,而没有任何保留(或依赖)周期:
class UserManager {private let commentManager: CommentManagerinit(commentManager: CommentManager) {self.commentManager = commentManager}func userDidPostComment(_ comment: Comment) {user.totalNumberOfComments += 1}
}
  • 我们又一次的移除了所有的可选类型,代码也就变得更好预测。

五、优雅的崩溃(Crashing gracefully)

  • 通过上面几个例子,我们通过对代码做一些调整,移除了可选类型从而排除了不确定性。然而,有的时候,移除可选类型是不可能的。来举个例子,比如在加载一个本地的包含针对 App 的配置项的 JSON 文件,这个操作本身一定会存在失败的情况,就需要添加错误处理。
  • 继续上面这个场景,加载配置文件失败的时候继续执行代码就会使得 App 进入一个未知状态,在这种情况下,最好的方式让它崩溃。这样,我们会得到一个崩溃日志,希望这个问题能够在用户感知之前早早的被我们的测试人员以及 QA 处理掉。
  • 所以,如何崩溃的呢?最简单的方式就是添加 ! 操作符,针对这个可选值强制解包,就会在其是 nil 的时候发生崩溃:
let configuration = loadConfiguration()!
  • 虽然这个方法比较简单,但是它有个比较大的问题,就是一旦这段代码崩溃,能得到的只有一个错误信息:
fatal error: unexpectedly found nil while unwrapping an Optional value
  • 这个错误信息并不告诉我们为什么发生这个错误,在哪里发生的,给不了我们什么线索来解决它。这个时候,就可以使用 guard 关键字,结合 preconditionFailure() 函数,在程序退出的时候给出定制消息:
guard let configuration = loadConfiguration() else {preconditionFailure("Configuration couldn't be loaded. " +"Verify that Config.JSON is valid.")
}
  • 上面这段代码发生崩溃的时候,就能获得更多更有效的错误信息:
fatal error: Configuration couldn’t be loaded. Verify that Config.JSON is valid.: file /Users/John/AmazingApp/Sources/AppDelegate.swift, line 17
  • 这样,现在就有了一个更清晰的解决问题的办法,能够准确的知道这个问题在代码里的哪个未知发生的。

六、引入 Require 库

  • 使用上面的 guard-let-preconditionFailure 的方案还是有一些冗长,确实让我们代码更难驾驭和理解。我们真的不想在代码中给这样的特殊情况留出太多空间,想更专注于代码逻辑上。
  • 我的解决方案就是使用 Require,它只是简单的在可选值添加简单的 require() 方法,但能够使得调用的地方更简洁。用这种方法来处理上面加载 JSON 文件的代码就可以这样写:
let configuration = loadConfiguration().require(hint: "Verify that Config.JSON is valid")
  • 当出现异常的时候,会给出下面的错误信息:
fatal error: Required value was nil. Debugging hint: Verify that Config.JSON is valid: file /Users/John/AmazingApp/Sources/AppDelegate.swift, line 17
  • Require 的另一个优势就是它和调用 preconditionFailure() 方法一样也会抛异常 NSException,就能使得那些异常上报工具能够捕获异常发生时候的元数据。
  • 如果想在自己代码中使用的话,Require 现在在 Github 上已经开源:Require。

七、总结

  • 所以,在 Swift 语言里处理那些非可选的可选值:
    • lazy 属性要比非可选的可选值要更好;
    • 适当的依赖管理要比非可选的可选值要好;
    • 当使用非可选的可选值的时候,优雅的崩溃。

Swift之深入解析如何处理非可选的可选项类型相关推荐

  1. Swift之深入解析如何避免单元测试中的强制解析

    一.前言 强制解析(使用 !)是 Swift 语言中不可或缺的一个重要特点(特别是和 Objective-C 的接口混合使用时),它回避了一些其他问题,使得 Swift 语言变得更加优秀. 比如在我的 ...

  2. Swift之深入解析可选类型Optional的底层原理

    一.Optional 简介 Swift 的可选(Optional)类型,用于处理值缺失的情况,可选表示"那儿有一个值,并且它等于 x "或者"那儿没有值". S ...

  3. 初探swift语言的学习笔记二(可选类型?和隐式可选类型!)

    作者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/28904115 转载请注明出处 如果觉得文章对你有所帮助,请通过留言 ...

  4. 窥探Swift之新添数据类型元组与可选值

    今天的博客中就总结一下关于Swift中相对Objc新添加的两个数据类型:元组(Tuple)和可选值类型(Optional).上面这两个类型是Swift独有的类型,使用起来也是非常方便的,今天就通过一些 ...

  5. Swift之深入解析如何使用Xcode和LLDB v2修改UI元素

    一.前言 在上一篇博客中,已经详细地介绍如何使用 LLDB 表达式修改 UI 元素,具体请参考:Swift之深入解析如何将代码添加为自定义LLDB命令. 在这篇博客中,将继续讨论相同的问题需求,并将重 ...

  6. Swift 类似HandyJSON解析Struct

    Swift 类似HandyJSON解析Struct HandyJSON 从源码解析Struct 获取TargetStructMetadata 获取TargetStructDescriptor 实现Ta ...

  7. Swift之深入解析可选链的功能和使用

    一.什么是可选链? 可选链(Optional Chaining)是一种可以请求和调用属性.方法和子脚本的过程,用于请求或调用的目标可能为nil. 可选链返回两个值: 如果目标有值,调用就会成功,返回该 ...

  8. Swift中文教程(十七) 可选链

    可选链(Optional Chaining)是一种可以请求和调用属性.方法及子脚本的过程,它的自判断性体现于请求或调用的目标当前可能为空(nil).如果自判断的目标有值,那么调用就会成功:相反,如果选 ...

  9. Swift之深入解析构造过程和析构过程

    一.Swift 构造过程 构造过程是为了使用某个类.结构体或枚举类型的实例而进行的准备过程,这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务. Swift 构造函数使用 in ...

最新文章

  1. HTML和CSS基础知识
  2. P4915 帕秋莉的魔导书
  3. 设计模式六大原则_设计模式—设计六大原则
  4. 【POJ - 3352】Road Construction(Tarjan,边双连通分量)
  5. PHP函数库06:PHP统计字符串里单词出现次数
  6. session.merge 缓存不更新_如何保证缓存与数据库双写时的数据一致性?
  7. 【娜家花园养花小记】
  8. 谷歌浏览器访问接口无返回
  9. flask中的csrf防御机制
  10. 《笨办法学python》第39课—— 列表的操作
  11. 手机无法打开html文件夹,手机网站在电脑上无法打开的解决办法
  12. 腾讯的职级系统——看清自己的职场宿命
  13. Arduino + RFID 读取 IC 卡 Arduino uno中获得RFID的UID 并通过串口转发RFID卡号
  14. 青春散场,初心不忘【致毕业季理想主义的你】
  15. 有关程序员的几则冷笑话
  16. 前端优化-前端性能优化
  17. android和js交互的代码,Android与JS代码交互
  18. python中formatter的用法_Python pyplot.FuncFormatter方法代码示例
  19. iOS 屏蔽系统更新描述文件更新!快把烦人的系统更新提示关掉!
  20. Java随笔-String有多长?

热门文章

  1. mysql netcdf_NetCDF官方读写终端ncdump和ncgen的用法
  2. Java开发常用的在线工具
  3. 计算机工资管理软件是,计件工资管理软件
  4. oracle 有iif么,ORACLE IIF声明
  5. 一加6手机可以把PDF文件转Word吗?
  6. 【Android Gradle 插件】build.gradle 中的 android 配置 ( 配置项 | compileSdkVersion 配置 | buildToolsVersion 配置 )
  7. 团队作业第五次—项目系统设计与数据库设计
  8. iRedmail配置手册
  9. 微型计算机2019年年度盘点,「2019 IT产业市场回顾」性能堆砌、轴之战再起、多声道普及!2019年游戏外设市场年终盘点...
  10. 微信小程序云函数操作云数据库Mysql