Swift 错误处理(Error handling)

作者: sunvlink | 来源:发表于2016-08-09 15:45 被阅读0次

    可选类型

    在swift程序中我们会处理各种各样的错误,比如说解析一个Dictionary:

      let dic = ["key": "value"]
      let value = dic["key"]
    

    value实际上是一个optional类型,也就是说value可能为String类型也可能是nil,处理这种情况我们一般对value尝试解包,如果成功就取里面的值:

        if let value = dic["key"] {
           print(value)
        }
    

    使用可选类型进行错误处理是一种方式,但是使用这种错误处理方式有一定的局限性。比如说我们想连接网络,如果没有连接上就返回一个nil,虽然可以但是我们只知道网络没连接上,但是却不知道没连接上的原因到底是服务端超时呢,还是客户端出现了问题,所以使用一套完善的错误处理机制能帮助我们更好的管理错误。

    表示并抛出错误

    举一个例子,假如我们需要读取磁盘上的某个文件进行处理,这个任务可能会有多种失败的情况,包括指定路径下文件并不存在,指定文件类型不正确等。我们的代码看起来是这样的:

    class FileManager {
    
      struct File: CustomStringConvertible {
          let name: String
          let type: String
          let isReadable: Bool
          let size: Int
        
          var description: String {
              return "\(name).\(type) size is : \(size) Bytes"
          }
      }
    
      private var files = ["path1": File(name: "file1", type: "text", isReadable: true, size: 3096),
                         "path2": File(name: "file2", type: "mp3", isReadable: false, size: 10240),
                         "path3": File(name: "file3", type: "flv", isReadable: true, size: 20480)
                         ]
    
      func readFile(path: String, type: String) -> File? {
        
          // 检查文件是否存在
          guard let file = files[path] else {
             return nil
          }
        
          // 检查文件是否有可读权限
          guard file.isReadable == true else {
              return nil
          }
        
          // 检查文件类型
          guard file.type == type else {
              return nil
          }
        
          return file
      }
    }
    

    我们定义了一个FileManager类,它有一个readFile(path: String, type: String) -> File? 方法,该方法传递一个路径和文件类型,返回一个可选类型,如果文件不存在或者没有可读权限或者类型不匹配都会返回nil,但是这并不是我们的想要的结果,我们想要的是读取文件失败的原因

    在swift中,错误用符合ErrorType协议的的类型的值来表示。代表一个可以抛出错误的类型。

    • 我们可以使用枚举来定义错误类型:

        enum ReadFileError: ErrorType {
          case FileNotExists   // 文件不存在
          case FileIsReadable  // 没有可读权限
          case TypeMismatch    // 类型不匹配
        }
      
    • 然后我们修改readFile()方法

      func readFile(path: String, type: String) throws -> File? {
        
        // 检查文件是否存在
        guard let file = files[path] else {
            throw ReadFileError.FileNotExists
        }
        
        // 检查文件是否有可读权限
        guard file.isReadable == true else {
            throw ReadFileError.FileNotReadable
        }
        
        // 检查文件类型
        guard file.type == type else {
            throw ReadFileError.TypeMismatch
        }
        
        return file
      }
      
    • 注意需要在返回类型->前面加上throws关键字,代表这是一个可能抛出异常的方法。

    • 然后在guard语句的else条件里面抛出错误,这样我们就能知道读取文件失败是什么原因导致的了。

    处理错误

    readFile(path: String, type: String)方法会传递出它抛出的所有错误,所有你要么使用do-catch语句,要么将错误继续传递下去。

      1. throwing函数传递错误

    比如我们想读取一个text类型的文件,我们的代码可能是这样的:

    func readTextFile(path: String) throws -> File? {
        return try readFile(path, type: "text")
    }
    

    然后我们尝试读取一个.text的文件:

    let fileManager = FileManager()
    if let file = try fileManager.readTextFile("path1") {
        print(file.description)
    }
    

    打印出:
    file1.text size is : 3096 Bytes

      1. 使用do-catch进行错误处理
    do {
        try fileManager.readFile("noSuchFilePath", type: "text")
    } catch {
        print(error)
    }
    

    打印出FileNotExists文件不存在的错误。

    如果我们想对具体的错误进行处理,可以这样:

      do {
          try fileManager.readFile("noSuchFilePath", type: "text")
      } catch ReadFileError.FileNotExists {
          print("File Not Exists")
      } catch ReadFileError.FileNotReadable {
          print("File Not Readable")
      } catch ReadFileError.TypeMismatch {
          print("Type Not Matched")
      } catch {
          print("unkown error")
      }
    

    别忘了在处理了所有错误之后在添加一个catch,因为有可能会有其他异常出现。

    如果我们的错误很多,难免会写很多catch语句,可以这样处理:

    do {
        try fileManager.readFile("noSuchFilePath", type: "text")
    } catch let error as ReadFileError {
        // 使用 switch 进行处理错误
    } catch {
        print("Unkown Error")
    }
    

    使用as关键字将ErrorType类型转换成ReadFileError 类型。

    defer语句

    defer表示即将在离开当前代码块时执行操作。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return或者break的语句。例如,你可以用defer语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。

    func processFile(file: File) {
        
        file.open()
        
        defer {
            file.close()
        }
    
        // 可能抛出错误的代码
        // ...
        // ...
    }
    

    比如上面(伪代码)我们读取了一个文件,然后需要对该文件进行调用open(),处理完之后我们需要关闭close(),但是中间可能会抛出错误,导致我们的文件打开了没有关闭,使用defer就能确保close()操作会被执行。代码块里面可以使用多个defer

    实际上defer是控制转移类关键字,它被当做其他语言(例如java)的finally关键字来使用。

    值得一提的是,当代码块里面有过多的控制转移这类的语句时,反而会导致程序非常的乱、可读性变差,所以一定要适当的使用。

    以此纪录swift学习之旅。

    相关文章

      网友评论

        本文标题:Swift 错误处理(Error handling)

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