如何使用 Swift 处理错误(FileManager 和其他一般)[关闭]
Posted
技术标签:
【中文标题】如何使用 Swift 处理错误(FileManager 和其他一般)[关闭]【英文标题】:How to handle errors with Swift (FileManager and others in general) [closed] 【发布时间】:2017-01-21 05:25:13 【问题描述】:注意:我之前发布了一个懒惰的问题,用于将代码转换为 Swift 3(已删除)
Apple 有一些用于管理文件的示例代码。这是一个古老的指南,全部在 Objective-C 中。我将 sn-p 转换为 Swift 3。我担心的是错误处理部分。我正在嵌套多个 do
/catch
块...只是想知道这是否是最佳的做事方式??
这个here有一个类似的问题/回答。
文档是:Apple File System Programming Guide,在“管理文件和目录”部分下。
这是我的代码(转换为 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....
);
有什么想法吗?
【问题讨论】:
“最佳方式”并不是一个 Stack Overflow 问题... 另外,您的代码不会模仿 Objective-C 原始代码的行为。这对你重要吗?这难道不是真正的目标吗?做与 Objective-C 原版相同的事情?好吧,你没有那样做。 【参考方案1】:您在删除现有备份后忘记重试复制操作。此外,“catch let error”可以写成“catch”,因为如果您不指定 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)")
【讨论】:
谢谢!像这样展示这两种方式真的可以澄清事情【参考方案2】:正如另一个答案中指出的那样,您对 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])
注意到什么了吗? 嵌套。因此,您的代码结构与原始代码结构之间的唯一区别是 what 是嵌套的。 Objective-C 原版嵌套了if
子句。你的 Swift 翻译创建了一个 do/catch 块的嵌套——因为它必须这样做,因为例如Objective-C copyItemAtURL
返回一个 BOOL,而 Swift copyItem(at:)
没有——如果有问题,它会抛出。
所以我认为我们可以得出结论,嵌套是完全正确的做法。实际上,您的代码有什么问题(原因是它不是对原始代码的准确翻译)是它嵌套的深度不够!
您可以尝试通过替换if
块来测试这是什么类型的错误,但您仍然会嵌套,所以为什么还要麻烦?你只会抛弃 do/catch 结构的所有优雅和清晰。
【讨论】:
好点,谢谢.. 上面的答案确实向我展示了您所指出的嵌套/错误处理。最后,这个文档确实澄清了问题,[Apple Error Handling Cocoa]developer.apple.com/library/content/documentation/Swift/…。总而言之,除了基本任务,我的错误处理理解不是很好 很好,很高兴你对此感到高兴。 @matt,Objective-C 嵌套 if 结构可以转换为 Swift 嵌套 if 结构(在注释中没有格式化:( ): let FM = FileManager if (try? FM.copyItem (at: appDataDir, to: backupDir)) == nil if (try?FM.removeItem(at: backupDir)) != nil if (try?FM.copyItem(at: appDataDir, to: backupDir)) ==无 @Owen 是的,我在回答中说可以模仿 if 结构。但关键是你还在嵌套。【参考方案3】:在 Swift 2 和 3 中,有 3 种方法可以使用可能产生错误的方法。
如果发生错误,请告诉我。
do
try something()
try somethingElse()
print("No error.")
catch
print("Error:", error)
我不在乎错误。如果发生错误,则返回 nil。
try? something()
我相信这不会有任何错误。如果发生错误,请让我的应用崩溃。
try! something()
【讨论】:
是的,但从 Apple 的代码中看起来,他们将fileManager.removeItem
链接到 fileManager.copyItem
内部。苹果在 cmets 中说明了原因,因为如果文件已经存在,fileManager.copyItem
可能会归档。所以我所做的是检查该错误并在那里调用fileManager.removeItem
。您的代码是有道理的,但不确定它是否会在这里实现 Apple 的初衷以上是关于如何使用 Swift 处理错误(FileManager 和其他一般)[关闭]的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spotify SDK 和 Swift 3 正确处理令牌刷新。错误代码 = 3840
如何使用原生崩溃报告在 Swift 4 中实现异常/错误处理?