美文网首页
使用defer以及串联either type

使用defer以及串联either type

作者: 醉看红尘这场梦 | 来源:发表于2020-03-15 17:50 被阅读0次

    在很多编程语言的错误处理机制中,除了“捕获”并处理异常之外,通常还会有一个finally的环节,让我们编写无论函数的执行是否顺利,在离开函数作用域的时候一定会执行的代码。Swift中也有一个类似的机制,叫做defer,只不过,它只保证在离开它所在的作用域时,内部的代码一定会被执行。你不用把它和try...catch放在一起。

    通过defer来计数

    例如,我们要统计所有Car对象的启动次数,就可以这样。先在Car中添加一个静态属性:

    struct Car {
        // ...
        static var startCounter: Int = 0
        // ...
    }
    
    

    然后,把start方法修改成这样:

    /// - Throws: `CarError` if the car is out of fuel
    func start() throws -> String {
        guard fuel > 5 else {
            throw CarError.outOfFuel(no: no, fuel: fuel)
        }
    
        defer { Car.startCounter += 1 }
    
        return "Ready to go"
    }
    
    

    注意到defer的用法了么?这样,无论start()是“抛出”了错误,还是正常返回,defer中的代码都会被执行,于是startCounter都会被加1。

    因此,只要你的函数有可能因为发生错误提前返回,就可以考虑使用这种模式来进行资源清理或回收的工作。

    串联多个either type

    接下来,我们回到之前提到的either type,相比do...catch,它有一个用法上的缺陷。在日常的编程中,我们经常会调用多个返回Result<T>类型的方法来完成一个复杂的操作。但这通常会导致嵌套很深的代码,我们得不断在上一个方法的.success情况里,继续后续的方法调用。于是,用不了太多方法,你就受不了自己编写的代码了。

    例如,对于上一节中提到的更新Car OS的例子,我们把更新的步骤更具体的表现出来。首先,添加一个新的CarError,表示文件校验错误:

    enum CarError: Error {
        case outOfFuel(no: String, fuel: Double)
        case updateFailed
        case integrationError
    }
    
    

    然后,为了通过编译,我们给Car添加两个函数,表示下载和校验文件,按照约定,它们都返回一个Result<T>表示执行结果:

    func downLoadPackage() -> Result<String> {
            return .failure(CarError.updateFailed)
        }
    
    func checkIntegration(of path: String) -> Result<Int> {
        return .failure(CarError.integrationError)
    }
    
    

    最后,我们来看下面这个让人痛苦的osUpdate实现方式:

    func osUpdate(postUpdate: @escaping (Result<Int>) -> Void) {
        DispatchQueue.global().async {
            // 1\. Download package
            switch self.downLoadPackage() {
            case let .success(filePath):
                // 2\. Check integration
                switch self.checkIntegration(of: filePath) {
                case let .success(checksum):
                    // 3\. Do you want to continue from here?
                    // ...
                case let .failure(e):
                    postUpdate(.failure(e))
                }
            case let .failure(e):
                postUpdate(.failure(e))
            }
        }
    }
    
    

    写到这里,我们仅仅完成了两个工作,在注释的第三步,你还有勇气继续再写下去么?为了解决either type的这个问题,我们得给它添加一些扩展。其实,这个问题和我们连续使用optional变量是非常类似的。既然为了不断使用optional的非nil值,我们可以使用flatMap串联,对于Result<T>,我们可以如法炮制一个:

    extension Result {
        func flatMap<U>(transform: (T) -> Result<U>) -> Result<U> {
            switch self {
            case let .success(v):
                return transform(v)
            case let .failure(e):
                return .failure(e)
            }
        }
    }
    
    

    之后,我们的osUpdate就可以改成这样:

    func osUpdate(postUpdate: @escaping (Result<Int>) -> Void) {
        DispatchQueue.global().async {
            let result = self.downLoadPackage()
                .flatMap {
                    self.checkIntegration(of: $0)
                }
                // Chain other processes here
    
            postUpdate(result)
        }
    }
    
    

    然后,我们可以使用flatMap继续串联任意多个后续需要执行的方法了,如果其中某个环节发生了错误,整个流程自然就结束了。这时,调用osUpdate的代码可以改成这样:

    Car(fuel: 10, no: "1").osUpdate(postUpdate: {
        switch $0 {
        case let .success(checksum):
            print("Update success: \(checksum)")
        case let .failure(e):
             if let e = e as? CarError {
                switch e {
                case .integrationError:
                    print("Checksum error")
                default:
                    break
                }
            }
    
            print("Update failed")
        }
    })
    
    

    最终,在postUpdate这个回调函数里,无论是成功还是失败,我们都可以得到osUpdate最后一步操作返回的结果。

    相关文章

      网友评论

          本文标题:使用defer以及串联either type

          本文链接:https://www.haomeiwen.com/subject/ilgfshtx.html