如何使用 Swift 处理错误(通常是 FileManager 和其他)
How to handle errors with Swift (FileManager and others in general)
注:我之前发过一个偷懒问题,把代码转成Swift3(删了)
Apple 有一些用于管理文件的示例代码。这是一本旧指南,全部在 Objective-C 中。我将代码片段转换为 Swift 3。我担心的是错误处理部分。 我正在嵌套多个 do
/catch
块。 .只是想知道这是否是最佳的做事方式?
有一个与此相似的question/amswer。
文档是:Apple File System Programming Guide,在第 "Managing Files and Directories".
部分下
这是我的代码(转换为Swift 3):
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch let error {
// If the operation failed again, abort for real.
print("Operation failed again, abort with error: \(error)")
}
} catch let error {
// If the operation failed again, abort for real.
print("Other error: \(error)")
}
}
}
这是我转换后的 Apple 文档中的代码:
- (void)backupMyApplicationData {
// Get the application's main data directory
NSArray* theDirs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([theDirs count] > 0)
{
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
NSURL* appSupportDir = (NSURL*)[theDirs objectAtIndex:0];
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL* appDataDir = [[appSupportDir URLByAppendingPathComponent:appBundleID]
URLByAppendingPathComponent:@"Data"];
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
NSURL* backupDir = [appDataDir URLByAppendingPathExtension:@"backup"];
// Perform the copy asynchronously.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
// Just try to copy the directory.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if ([theFM removeItemAtURL:backupDir error:&anError]) {
// If the operation failed again, abort for real.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// Report the error....
}
}
}
});
}
}
有什么想法吗?
在Swift2和3中,有3种方法使用可能产生错误的方法。
如果发生错误,请告诉我。
do {
try something()
try somethingElse()
print("No error.")
} catch {
print("Error:", error)
}
我不关心错误。如果发生错误,只是 return nil.
try? something()
我相信这不会有任何错误。如果发生错误,请让我的应用崩溃。
try! something()
您忘记在删除现有备份后重试复制操作。此外,"catch let error" 可以写成 "catch",因为如果您不指定捕获模式,错误将自动分配给名为 "error" 的常量。这是您的代码,其中包含这些更改:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// Error occurred because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch {
// The delete operation failed, abort.
print("Deletion of existing backup failed. Abort with error: \(error)")
return
}
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy operation failed again, abort.
print("Copy operation failed again. Abort with error: \(error)")
}
} catch {
// The copy operation failed for some other reason, abort.
print("Copy operation failed for other reason. Abort with error: \(error)")
}
}
}
如果您想要更接近 Objective-C 原文的翻译,其中只有一个错误输出,试试这个:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
// Just try to copy the directory.
if (try? fileManager.copyItem(at: appDataDir, to: backupDir)) == nil {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if (try? fileManager.removeItem(at: backupDir)) != nil {
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy retry failed.
print("Failed to backup with error: \(error)")
}
}
}
}
}
您对 Objective-C 原文的翻译不正确,已在另一个答案中指出。但是,这似乎不是您的真正问题。看来你的问题是关于嵌套的。
要回答这个问题,请看您要模仿的原作:
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
if ([theFM removeItemAtURL:backupDir error:&anError]) {
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
}
}
}
注意到什么了吗? 嵌套。所以你的代码结构和原来的结构唯一的区别就是什么是嵌套的。 Objective-C 原文嵌套了 if
个子句。您的 Swift 翻译嵌套了 do/catch 块——它必须这样做,因为例如Objective-C copyItemAtURL
returns 一个 BOOL 而 Swift copyItem(at:)
没有——如果有问题,它会抛出。
所以我认为我们可以得出结论,嵌套完全是正确的做法。事实上,您的代码有什么问题(它不是原始翻译的准确原因)是它嵌套不够深!
您可以尝试通过替换 if
块测试这是什么类型的错误来尝试使 catch
块中的至少一个不存在,但您仍然会嵌套,所以为什么烦?您只会丢掉 do/catch 结构的所有优雅和清晰。
注:我之前发过一个偷懒问题,把代码转成Swift3(删了)
Apple 有一些用于管理文件的示例代码。这是一本旧指南,全部在 Objective-C 中。我将代码片段转换为 Swift 3。我担心的是错误处理部分。 我正在嵌套多个 do
/catch
块。 .只是想知道这是否是最佳的做事方式?
有一个与此
文档是:Apple File System Programming Guide,在第 "Managing Files and Directories".
部分下这是我的代码(转换为Swift 3):
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch let error {
// If the operation failed again, abort for real.
print("Operation failed again, abort with error: \(error)")
}
} catch let error {
// If the operation failed again, abort for real.
print("Other error: \(error)")
}
}
}
这是我转换后的 Apple 文档中的代码:
- (void)backupMyApplicationData {
// Get the application's main data directory
NSArray* theDirs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([theDirs count] > 0)
{
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
NSURL* appSupportDir = (NSURL*)[theDirs objectAtIndex:0];
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL* appDataDir = [[appSupportDir URLByAppendingPathComponent:appBundleID]
URLByAppendingPathComponent:@"Data"];
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
NSURL* backupDir = [appDataDir URLByAppendingPathExtension:@"backup"];
// Perform the copy asynchronously.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
// Just try to copy the directory.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if ([theFM removeItemAtURL:backupDir error:&anError]) {
// If the operation failed again, abort for real.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// Report the error....
}
}
}
});
}
}
有什么想法吗?
在Swift2和3中,有3种方法使用可能产生错误的方法。
如果发生错误,请告诉我。
do { try something() try somethingElse() print("No error.") } catch { print("Error:", error) }
我不关心错误。如果发生错误,只是 return nil.
try? something()
我相信这不会有任何错误。如果发生错误,请让我的应用崩溃。
try! something()
您忘记在删除现有备份后重试复制操作。此外,"catch let error" 可以写成 "catch",因为如果您不指定捕获模式,错误将自动分配给名为 "error" 的常量。这是您的代码,其中包含这些更改:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// Error occurred because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch {
// The delete operation failed, abort.
print("Deletion of existing backup failed. Abort with error: \(error)")
return
}
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy operation failed again, abort.
print("Copy operation failed again. Abort with error: \(error)")
}
} catch {
// The copy operation failed for some other reason, abort.
print("Copy operation failed for other reason. Abort with error: \(error)")
}
}
}
如果您想要更接近 Objective-C 原文的翻译,其中只有一个错误输出,试试这个:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
// Just try to copy the directory.
if (try? fileManager.copyItem(at: appDataDir, to: backupDir)) == nil {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if (try? fileManager.removeItem(at: backupDir)) != nil {
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy retry failed.
print("Failed to backup with error: \(error)")
}
}
}
}
}
您对 Objective-C 原文的翻译不正确,已在另一个答案中指出。但是,这似乎不是您的真正问题。看来你的问题是关于嵌套的。
要回答这个问题,请看您要模仿的原作:
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
if ([theFM removeItemAtURL:backupDir error:&anError]) {
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
}
}
}
注意到什么了吗? 嵌套。所以你的代码结构和原来的结构唯一的区别就是什么是嵌套的。 Objective-C 原文嵌套了 if
个子句。您的 Swift 翻译嵌套了 do/catch 块——它必须这样做,因为例如Objective-C copyItemAtURL
returns 一个 BOOL 而 Swift copyItem(at:)
没有——如果有问题,它会抛出。
所以我认为我们可以得出结论,嵌套完全是正确的做法。事实上,您的代码有什么问题(它不是原始翻译的准确原因)是它嵌套不够深!
您可以尝试通过替换 if
块测试这是什么类型的错误来尝试使 catch
块中的至少一个不存在,但您仍然会嵌套,所以为什么烦?您只会丢掉 do/catch 结构的所有优雅和清晰。