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 ...
随机推荐
- _MainTex_TexelSize what's the meaning?
uniform float4 _MainTex_TexelSize where is the value of the float4 _MainTexelSize from? It's set by ...
- ICG游戏:证明,先手不是必胜就是必败。
简介: ICG游戏:Impartial Combinatorial Games,公平的组合游戏. 以下是定义,来自网络,可能不够严谨: 1.两名选手:2.两名选手轮流行动,每一次行动可以在有限合法操作 ...
- php 共享内存学习(shmop函数)
问题:希望可以在进程间共享变量,为共享数据提供快速访问 解决方案:除了可以使用APC模块,还可以用shmop或System V共享内存 //创建键 //将一个可访问的文件路径名转换为一个可供 shmo ...
- QuartJob的CronExpressionString规则详解
字段 允许值 允许的特殊字符 秒 0-59 , - * / 分 0-59 , - * / 小时 0-23 , - * / 日期 1-31 , - * ? ...
- [SoapUI]怎样保存response到本地文件夹
def myOutFile = "D:/AUS/Aspect Huntley feed URLs/Automation Save Responses/ahresearch.xml" ...
- Oracle GoldenGate 一、介绍和安装
1 说明 1.1 下载和文档说明 下载地址:http://www.oracle.com/technetwork/middleware/goldengate/downloads/index.html 我 ...
- 16 Finding a Protein Motif
Problem To allow for the presence of its varying forms, a protein motif is represented by a shorthan ...
- 服务器上如何将D盘修改为E盘
1.计算机管理→磁盘管理 2.右键点击需要调整的磁盘→更改驱动器号和路径 3.在弹出的设置框中→更改 4.点击右边的下拉箭头▼→选择一个盘符→确定 注意:如果盘符混乱,需要理顺,因为有些盘符占有了,不 ...
- jquery怎么根据后台传过来的值动态设置下拉框、单选框选中
$(function(){ var sex=$("#sex").val(); var marriageStatus=$("#marriageStatus").v ...
- mongo学习- mapReduce操作事例
源数据: { "_id" : 1.0, "name" : "abc", "age" : 43.0, "type ...