我对Swift的了解不是很深,但有一件事情引起了我的注意,那就是Swift中没有异常。那么在Swift里如何处理错误呢?有人发现了与错误处理相关的内容吗?
我对Swift的了解不是很深,但有一件事情引起了我的注意,那就是Swift中没有异常。那么在Swift里如何处理错误呢?有人发现了与错误处理相关的内容吗?
Swift 2有一些变化,引入了一个新的错误处理机制,该机制与异常有些类似但细节不同。
如果函数/方法想要表明它可能会抛出错误,它应该包含throws
关键字,如下所示:
func summonDefaultDragon() throws -> Dragon
注意:该函数可以抛出任何实现ErrorType接口的类型的实例或者根本不会抛出。此声明仅说明该函数可能会抛出异常。
为了调用该函数,您需要使用try关键字,像这样:
try summonDefaultDragon()
通常应该像这样使用 do-catch 块
do {
let dragon = try summonDefaultDragon()
} catch DragonError.dragonIsMissing {
// Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
// Other specific-case error-handlng
} catch {
// Catch all error-handling
}
注意:捕获子句使用了 Swift 模式匹配的所有强大功能,因此您在此非常灵活。如果您正在从一个被标记为 throws
关键字的函数中调用一个抛出异常的函数,则可以决定传播该错误:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
或者,您可以使用try?
调用throwing函数:
let dragonOrNil = try? summonDefaultDragon()
这种方式可以让你获得返回值或者nil,如果出现任何错误。使用这种方式,你不会得到错误对象。
这意味着你也可以将try?
与其他有用的语句结合使用,例如:
if let dragon = try? summonDefaultDragon()
或者guard let dragon = try? summonDefaultDragon() else { ... }
最后,你可以判断出错误实际上不会发生(例如因为你已经检查了先决条件),并使用 try!
关键字:
let dragon = try! summonDefaultDragon()
如果函数实际上抛出了错误,那么您的应用程序将会在运行时出现错误并终止。
为了抛出一个错误,您可以像这样使用throw关键字。
throw DragonError.dragonIsMissing
你可以抛出任何符合 ErrorType
协议的东西。首选 NSError
符合此协议,但您可能想选择基于枚举的 ErrorType
,它使您能够将多个相关错误分组,可能带有其他数据片段,例如:
enum MyError: Error {
case networkError
case serverError(String)
case parsingError(lineNumber: Int)
}
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Swift 2和3的错误处理机制与Java/C#/C++的异常处理有以下主要差异:
do-catch
+try
+defer
来取代传统的try-catch-finally
语法。do-catch
块将无法捕获任何NSException异常,反之亦然,您必须使用ObjC。NSError
方法约定兼容,返回false
(用于返回Bool
的函数)或nil
(用于返回AnyObject
的函数),并传递NSErrorPointer
以获取错误详细信息。为了更轻松地处理错误,还有两个概念:
defer
关键字的延迟操作,它们允许您实现与Java/C#等中的finally块相同的效果。guard
关键字的守卫语句,它允许您编写比普通错误检查/信号代码更少的if/else代码。运行时错误:
对于处理运行时错误(如网络连接问题、数据解析、打开文件等),建议像在ObjC中一样使用NSError
进行处理,因为Foundation、AppKit、UIKit等会以这种方式报告其错误。因此,这更多是框架相关而非语言相关的问题。
另一个常用模式是使用类似于AFNetworking中的成功/失败分离块:
var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
success: { (NSURLSessionDataTask) -> Void in
println("Success")
},
failure:{ (NSURLSessionDataTask, NSError) -> Void in
println("Failure")
})
尽管如此,NSError
实例仍经常在失败块中被接收,描述了错误信息。
程序员错误:
对于程序员错误(例如数组元素越界、向函数调用传递无效参数等),您可以在ObjC中使用异常。Swift语言似乎没有任何语言支持异常(如throw
、catch
等关键字)。但是,正如文档所建议的那样,它运行在与ObjC相同的运行时上,因此您仍然可以像这样抛出NSExceptions
:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
仅使用纯Swift无法捕获它们,不过您可以选择在ObjC代码中捕获异常。
问题在于您是否应该为程序员错误抛出异常,还是像Apple在语言指南中建议的那样使用断言。
fatalError(...)
也是一样的。 - holexNSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
NSLog(@"Error: %@", error.domain);
}
let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
print ("Error: \(error.domain)")
}
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
enum ServerResponse {
case Result(String, String)
case Error(String)
}
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
let serverResponse = "Failure... \(error)"
}
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
encoding: NSUTF8StringEncoding,
error: &writeError)
if !written {
if let error = writeError {
println("write failure: \(error.localizedDescription)")
}
}
Swift 中没有异常(Exceptions)的概念,这和 Objective-C 的处理方式相似。
在开发中,你可以使用 assert
来捕获任何可能出现的错误,并在进入生产前修复它们。
经典的 NSError
方法没有变化,你需要发送一个 NSErrorPointer
,然后它会被填充。
简单示例:
var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
println("An error occurred \(error)")
} else {
println("Contents: \(contents)")
}
f();g();
都会变成 f(&err);if(err) return;g(&err);if(err) return;
在第一个月内,然后就变成了 f(nil);g(nil);hopeToGetHereAlive();
。 - hariseldon78func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}
var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
println("write failure 1: \(writeError!.localizedDescription)")
// assert(false) // Terminate program
}
然而,我更喜欢使用try/catch,因为我觉得这样更容易理解,因为它将错误处理移动到了一个单独的块中,这种安排有时被称为“黄金路径”。幸运的是,你可以通过使用闭包来实现这一点:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
此外,添加重试功能也非常容易:
TryBool {
write("~/Error3")(error: $0) // The code to try
}.retry {
println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
return write("~/Error3r") // The code to retry
}.catch {
println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
class TryBool {
typealias Tryee = NSErrorPointer -> Bool
typealias Catchee = NSError? -> ()
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return self.retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) {
var error: NSError?
for numRetries in 0...retries { // First try is retry 0
error = nil
let result = tryee(&error)
if result {
return
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
catchee(error)
}
}
class TryOptional<T> {
typealias Tryee = NSErrorPointer -> T?
typealias Catchee = NSError? -> T
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) -> T {
var error: NSError?
for numRetries in 0...retries {
error = nil
let result = tryee(&error)
if let r = result {
return r
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
return catchee(error)
}
}
TryOptional 版本强制使用非 Optional 返回类型,从而使后续编程更加容易,例如:“Swift Way”:
struct FailableInitializer {
init?(_ id: Int, error: NSErrorPointer) {
// Always fails in example
if error != nil {
error.memory = NSError(domain: "", code: id, userInfo: [:])
}
return nil
}
private init() {
// Empty in example
}
static let fallback = FailableInitializer()
}
func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
return FailableInitializer(id, error: error)
}
var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
println("failableInitializer failure code: \(failError!.code)")
failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap
使用TryOptional:
let failure2 = TryOptional {
failableInitializer(2)(error: $0)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
let failure3 = TryOptional {
failableInitializer(3)(error: $0)
}.retry {
println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
return failableInitializer(31)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
var fooError : NSError ? = nil
let someObject = foo(aParam, error:&fooError)
// Check something was returned and look for an error if it wasn't.
if !someObject {
if let error = fooError {
// Handle error
NSLog("This happened: \(error.localizedDescription)")
}
} else {
// Handle success
}`
编写一个接受错误参数的函数...
func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {
// Do stuff...
if somethingBadHasHappened {
if error {
error.memory = NSError(domain: domain, code: code, userInfo: [:])
}
return nil
}
// Do more stuff...
}
这是一个关于Objective C的基本封装,它提供了try catch功能。 https://github.com/williamFalcon/SwiftTryCatch
使用方法如下:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
managedContext.executeFetchRequest(fetchRequest, error: &error)
中,现在只需使用managedContext.executeFetchRequest(fetchRequest)
并使用try
、catch
处理错误。详细信息请参阅Apple文档链接。do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
例如:错误处理模型:Swift 2.0中的新错误处理模型将立即感觉自然,具有熟悉的try、throw和catch关键字。最重要的是,它被设计为与Apple SDK和NSError完美配合。事实上,NSError符合Swift的ErrorType。您肯定会想观看WWDC会议上关于Swift新功能的更多内容。
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/
总结一下:// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData
// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
{
completionHandler()
}
self.loadData("someString",
completionHandler:
{ result: LoadDataResult in
do
{
let data = try result()
// success - go ahead and work with the data
}
catch
{
// failure - look at the error code and handle accordingly
}
})