Swift 函数提前返回
简评:函数提前返回主要的好处是:将每个错误处理进行分离,审查代码时不需要考虑多种复杂异常,我们可以吧注意力集中在也业务逻辑中,调试代码时可以直接在异常中打断点。
提前返回
首先来看一下需要改进的代码示例,我们构建一个笔记应用使用 NotificationCenter API,当笔记内容有变化时 Notification 来通知笔记列表变更,代码如下:
class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
let noteInfo = notification.userInfo?["note"] as? [String : Any]
if let id = noteInfo?["id"] as? Int {
if let note = database.loadNote(withID: id) {
notes[id] = note
tableView.reloadData()
}
}
}
}
上面的代码可以很好的工作,但是可读性差了点。因为这段代码包含多重缩进和类型转换。我们来尝试改进这段代码。
- 将方法提前返回,让我们函数尽可能的快的返回。
- 使用 guard 替代 if,以避免嵌套。
class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
let noteInfo = notification.userInfo?["note"] as? [String : Any]
guard let id = noteInfo?["id"] as? Int else {
return
}
guard let note = database.loadNote(withID: id) else {
return
}
notes[id] = note
tableView.reloadData()
}
}
将函数提前返回能够将功能失败的情况处理得更加清晰,这不仅提高了可读性(更少的缩进,更少的嵌套),同时也有利于单元测试。
我们可以进一步改进代码,将获取 noteID 和类型转换的代码放在 Notification Extension 中,这样就将 handleChangeNotification 业务逻辑和具体细节分离开来。修改后代码如下所示:
private extension Notification {
var noteID: Int? {
let info = userInfo?["note"] as? [String : Any]
return info?["id"] as? Int
}
}
class NoteListViewController: UIViewController {
@objc func handleChangeNotification(_ notification: Notification) {
guard let id = notification.noteID else {
return
}
guard let note = database.loadNote(withID: id) else {
return
}
notes[id] = note
tableView.reloadData()
}
}
这种结构还大大简化了调试的难度,我们可以直接在每个 guard 中 return 中添加断点来截获所有失败情况,而不需要单步执行所有逻辑。
条件构造
当构造一个对象实例,非常普遍的需求是需要构建哪类对象取决于一系列的条件。
例如,启动应用程序时显示哪个 view controller 取决于:
- 是否已经登录。
- 用户是否已经完成入职流程(onboarding flow)。
我们对这些条件的的实现可能是一系列的 if 和 else 语句,如下所示:
func showInitialViewController() {
if loginManager.isUserLoggedIn {
if tutorialManager.isOnboardingCompleted {
navigationController.viewControllers = [HomeViewController()]
} else {
navigationController.viewControllers = [OnboardingViewController()]
}
} else {
navigationController.viewControllers = [LoginViewController()]
}
}
同样的提前返回和 guard 语句可以提升代码可读性,但是现在这种情况不是处理失败情况,而是在不同条件下构建不同 view controller。
现在来改进这段代码,使用轻量级的工程模式,将构造初始界面移动到专门的函数中,该函数返回匹配条件的view controller。如下所示:
func makeInitialViewController() -> UIViewController {
guard loginManager.isUserLoggedIn else {
return LoginViewController()
}
guard tutorialManager.isOnboardingCompleted else {
return OnboardingViewController()
}
return HomeViewController()
}
func showInitialViewController() {
let viewController = makeInitialViewController()
navigationController.viewControllers = [viewController]
}
由于 makeInitialViewController 方法是个纯函数(不影响外部状态,固定输入能够得到固定输出),实际上影响外部状态的只有一个地方 navigationController.viewControllers = [viewController] ,(在日常开发中状态如果没有得到很好的控制很容易引起 bug,所以使用更少状态和减少对状态的修改可以一定程度上减少 bug 出现的几率)。
条件控制
最后我们来看看,函数如何简化复杂的条件逻辑。我们来构建一个 view controller 来显示社交应用的评论功能,如果满足三个条件则运行用户对评论进行编辑。代码如下:
class CommentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if comment.authorID == user.id {
if comment.replies.isEmpty {
if !comment.edited {
let editButton = UIButton()
...
view.addSubview(editButton)
}
}
}
...
}
}
这里使用了 3 个 if 嵌套逻辑,每次重新审查代码都会比较困扰,更具之前的经验我们可以对代码进行优化,添加 Comment extension:
extension Comment {
func canBeEdited(by user: User) -> Bool {
guard authorID == user.id else {
return false
}
guard comment.replies.isEmpty else {
return false
}
return !edited
}
}
class CommentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if comment.canBeEdited(by: user) {
let editButton = UIButton()
...
view.addSubview(editButton)
}
...
}
}
Swift 函数提前返回的更多相关文章
- Swift函数编程之Map、Filter、Reduce
在Swift语言中使用Map.Filter.Reduce对Array.Dictionary等集合类型(collection type)进行操作可能对一部分人来说还不是那么的习惯.对于没有接触过函数式编 ...
- 4 .Swift函数|闭包
在编程中,我们常把能完成某一特定功能的一组代码,并且带有名字标记类型叫做函数,在C语言中,我们知道函数名就是一个指针,它指向了函数体内代码区的第一行代码的地址,在swift中也具有同样的功效. 在Sw ...
- Swift 函数
1: 函数形式: Swift函数以关键字func 标示.返回类型->后写明.如果没有返回类型可以省去.多个参数用,分割.其中参数名字在前:类型描述 func GetName(strName:St ...
- Swift函数|闭包
在编程中,我们常把能完成某一特定功能的一组代码,并且带有名字标记类型叫做函数,在C语言中,我们知道函数名就是一个指针,它指向了函数体内代码区的第一行代码的地址,在swift中也具有同样的功效. 在Sw ...
- javascript . 03 函数定义、函数参数(形参、实参)、函数的返回值、冒泡函数、函数的加载、局部变量与全局变量、隐式全局变量、JS预解析、是否是质数、斐波那契数列
1.1 知识点 函数:就是可以重复执行的代码块 2. 组成:参数,功能,返回值 为什么要用函数,因为一部分代码使用次数会很多,所以封装起来, 需要的时候调用 函数不调用,自己不会执行 同名函数会覆盖 ...
- python10--函数的来源,优点,定义,组成,使用(定义,调用)函数的分类,函数的返回值
一.函数 # *****# 函数:完成 特定 功能的代码块,作为一个整体,对其进行特定的命名,该名字就代表函数# -- 现实中:很多问题要通过一些工具进行处理 => 可以将工具提前生产出 ...
- day10函数,函数的使用,函数的分类,函数的返回值
函数 # ***** # 函数:完成 特定 功能的代码块,作为一个整体,对其进行特定的命名,该名字就代表函数 # -- 现实中:很多问题要通过一些工具进行处理 => 可以将工具提前生产出来并命名 ...
- Swift2.0语言教程之函数的返回值与函数类型
Swift2.0语言教程之函数的返回值与函数类型 Swift2.0中函数的返回值 根据是否具有返回值,函数可以分为无返回值函数和有返回值函数.以下将会对这两种函数类型进行讲解. Swift2.0中具有 ...
- swift 函数.和匿名函数
函数 注意: 没有定义返回类型的函数会返回特殊的值,叫 Void.它其实是一个空的元组(tuple),没有任何元素,可以写成(). 使用元组作为返回参数,返回多个参数 func count(strin ...
随机推荐
- ORM是什么?如何理解ORM?
一.ORM简介 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.简单的说,ORM是通过使 ...
- 15.3Sum (Two-Pointers)
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all un ...
- Windows Python 2.7环境搭建
一.安装及修改环境变量 我安装的版本是python-2.7.15.amd64,因为2.7.9之后的版本都会安装好pip.将Python执行文件所在文件夹加入path路径,C:\Python27.将pi ...
- unix时间戳与时间
[root@pserver ~]# date -d "@1381371010" Thu Oct :: CST [root@pserver ~]# date --date=" ...
- 《剑指offer》读书笔记
二叉树 重建二叉树 面试题6:(p55) 题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树. 假设输入的前序遍历和中序遍历的结果中都不含重复的数字. 例如输入前序遍历序列{1, 2, 4 ...
- macOS 安装设置 Tunnelblick (openvpn 替代)
1、下载.安装 2、准备好 openvpn 相关的文件,全部放在一个目录中.主要有以下几个: ca.crt client.crt client.key client.ovpn 3、打开 client. ...
- MVC5数据库迁移命令!
首先数据库迁移在上下文里设置要设置成为CreateDatabaseIfNotExists, 然后在Nuget控制平台输入命令 在“程序包管理器控制台”窗口中输入:Enable-Migrations ...
- Gym 101201F Illumination (Two-Sat)
题意:一个n*n的房子,有很多灯,每个格子只能被上下方向照一次.左右方向照一次,每个灯可以选择上下或是左右照,照明长度以自身位置为中心,占用2*r+1个格子.问能否安排一种方案,使所有格子满足条件. ...
- webapi Model Validation 模型验证
通常情况下,对于那些经常为别人提供数据接口的开发人员来说,对于调用方传递过来的参数都会有验证处理.例如: if (string.IsNullOrEmpty(entity.Name)) { //当姓名为 ...
- 匹配yyyy-mm-dd日期格式的的正则表达式[转]
转http://www.jb51.net/article/28034.htm 今天头让我修改个javascript方法,验证输入的日期是否符合要求.恩.我们的要求是yyyy-mm-dd这样的格式,其他 ...