Swift 错误处理(Error handling)

可选类型

在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))