如何使用 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种方法使用可能产生错误的方法。

  1. 如果发生错误,请告诉我。

    do {
        try something()
        try somethingElse()
        print("No error.")
    } catch {
        print("Error:", error)
    }
    
  2. 我不关心错误。如果发生错误,只是 return nil.

    try? something()
    
  3. 我相信这不会有任何错误。如果发生错误,请让我的应用崩溃。

    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 结构的所有优雅和清晰。