代码写多了就想优化,这是一个天然的过程。近期在代码优化方面积累了一些心得,会慢慢整理出来。
本文主要适用于想要缩减代码行数及规范化逻辑和错误的场景。
首先回忆下在OC中是如何处理错误和逻辑的,下面罗列两种常见的处理方式。
1、方法定义返回值,根据返回值判断成功还是失败。复杂情况下定义枚举可以覆盖更多业务场景。
+ (BOOL) isFileExist:(NSString *)filePath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL result = [fileManager fileExistsAtPath:filePath];
return result;
}
2、通过NSError的指针写入,判断NSError不为空获取错误信息。
NSError *error;
BOOL success = [data writeToFile: path options: options error: &error];
if(error) {
// 发生了错误
}
但实际情况是,很多时候不会出什么问题,所以不少开发会图省事直接给error赋值nil。
下面介绍Swift中高逼格的用法。也就是关键字 throws
和guard
的应用。
简单描述下例子场景:编写一个方法,通过Index获取数组中的对象。
let item = array[index]
在一般情况下为了代码可靠性和健壮性,会做一些非空判断和逻辑判断。根据返回值来绝对本次操作是否成功。
func getObjectByIndex(index:Int) -> Int {
let array = [1,2,3,4,5]
if index > 0{
if array.count > 0{
if index < array.count {
return array[index]
}
}
}
return -1
}
这里有三层判断,但在实际项目开发中我见过10层以上的嵌套,以至于后面版本迭代逻辑时很容易挑错在哪个代码块里。
而且即使最后返回-1,谁也不敢保证数据源里真的有一个合法的-1被正确的返回出来了。也许有人会想到定义枚举来更细致的区分错误,但这个例子中又和返回数据冲突了。。。。
Swift中使用guard
配合throws
可以很便捷的解决这个问题
guard
的知识比较基础,已经了解的同学可以跳过直接往下看。
先说guard
的用法,字面意思是守护 警卫。
很形象的比喻:当你走进一个大门,门口一个警卫站着,看你有问题就拦下你,没问题就放你过去。
当guard关键字后的表达式为false时,就会执行else后的代码块,否则就继续往下执行
//条件为true,else后的代码块不会执行
guard 1 == 1 else { return }
//条件为false,else后的代码块会执行
guard 1 < 1 else { return }
很好理解不是么,熟练应用后可以有效减少代码的嵌套。
继续改造上面的例子,首先定义一些错误类型的枚举。
enum arrayError: Error {
case indexCrossBoard, indexLessZero, arrayIsEmpty
}
注:在Swift4.0中已经取消了ErrorType
关键字。目前统一继承Error
。
接下来在入参后面中加入throws
关键字标记该方法,在方法体内部使用throw
抛出具体的错误类型。
func getObjByIndex(index:Int) throws -> Int {
let array = [1,2,3,4,5]
//when false,execute code in black after else
guard index < array.count else { throw arrayError.indexCrossBoard }
guard index > 0 else { throw arrayError.indexLessZero }
guard array.count > 0 else { throw arrayError.arrayIsEmpty }
return array[index]
}
可以看到值返回和错误返回已经被区分开了,当一个方法体被throws
关键字标记后的方法代表它可能会向外抛出错误,这个错误可以是自定义的,可以取自上面自定义的枚举。
有抛出就一定有接收,所以方法在被调用时会被强制加上do catch try
关键字,不然编译器会报错。
do {
let item:Int = try getObjByIndex(index: 20)
} catch arrayError.indexCrossBoard {
print("Error of Corss board")
} catch arrayError.indexLessZero {
print("Error of Index less Zero")
} catch arrayError.arrayIsEmpty {
print("Array is Empty")
} catch {
}
相比较传统的NSError处理错误,这样的规范使得开发无法漠视操作中可能会带来的错误。比如磁盘满了,但任然尝试写入文件,排查了半天又不知道哪里出问题了,身边又围了很多QA和产品,你懂的。
而这种做法表面上看try catch一定程度上冗长了代码,但回想下我们之前处理不同的错误类型不也是要嵌套很多if判断来执行不同操作么。所以代码优化只是一定程度上的规整和增加可读性,该做的事还是少不了的。
以上是入门级用法,我们还可以尝试进行闭包抛出错误。
//Use throws mark Closure
typealias ArrayErrorCallback = () throws -> Bool
func checkObjectById(index:Int, errorBlock:@escaping (_ inner:ArrayErrorCallback) -> Void) {
let array = [1,2,3,4,5]
if index < array.count {
// throw error
errorBlock({ throw arrayError.indexCrossBoard })
}
// return value
errorBlock({return true})
}
override func viewDidLoad() {
super.viewDidLoad()
checkObjectById(index:20) { (inner: ArrayErrorCallback) -> Void in
do {
let success = try inner()
print(success)
} catch {
print(error)
}
}
}
用法差不多,只是把throws
标记在闭包上。更适合异步操作的场景。
基本就是这么多,大家可以回去翻阅下自己项目中的业务常见,看哪些地方适合这样的改动,本质上还是以适合为主,不建议强行装X。