如何使用 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 和其他一般)[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Swift 泛型处理成功和错误 API 响应?

如何使用 Spotify SDK 和 Swift 3 正确处理令牌刷新。错误代码 = 3840

如何使用原生崩溃报告在 Swift 4 中实现异常/错误处理?

如何在 swift 4 中处理 HTTP 加载失败(错误代码:-1009 [1:50])?

处理 Swift 2.0 中的转换错误

Swift 结合处理 HTTP 状态码错误