可选类型
在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
语句,要么将错误继续传递下去。
- 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
- 使用
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
学习之旅。