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学习之旅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容