Swift使用guard和throws更优雅的处理逻辑和错误

代码写多了就想优化,这是一个天然的过程。近期在代码优化方面积累了一些心得,会慢慢整理出来。

本文主要适用于想要缩减代码行数及规范化逻辑和错误的场景。

首先回忆下在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中高逼格的用法。也就是关键字 throwsguard的应用。

简单描述下例子场景:编写一个方法,通过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。

建了一个Swift的QQ交流群 859978655,欢迎大家加入。

你可能感兴趣的:(Swift使用guard和throws更优雅的处理逻辑和错误)