【问题标题】:How to overwrite a file with NSFileManager when copying?复制时如何使用 NSFileManager 覆盖文件?
【发布时间】:2011-09-02 12:07:45
【问题描述】:

我正在使用这种方法来复制文件:

[fileManager copyItemAtPath:sourcePath toPath:targetPath error:&error];

我想在文件已经存在时覆盖它。此方法的默认行为是抛出异常/错误“文件存在”。当文件存在时。没有选项可以指定它应该覆盖。

那么最安全的方法是什么?

我会先检查文件是否存在,然后将其删除,然后再尝试复制吗?这存在应用程序或设备在文件被删除后的纳秒内立即关闭但新文件尚未复制到该位置的危险。然后就什么都没有了。

也许我必须先更改新文件的名称,然后删除旧文件,然后重新更改新文件的名称?同样的问题。如果在这一纳秒内应用或设备关闭并且没有发生重命名怎么办?

【问题讨论】:

    标签: iphone ios ipad filesystems nsfilemanager


    【解决方案1】:

    如果您不能/不想将文件内容保留在内存中,但希望按照其他建议中所述进行原子重写,您可以首先将原始文件复制到临时目录到唯一路径(Apple 的文档建议使用临时目录),然后使用 NSFileManager 的

    -replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:

    根据参考文档,此方法“以确保不会发生数据丢失的方式替换指定 URL 处的项目内容”。 (来自参考文档)。需要将原始文件复制到临时目录,因为此方法会移动原始文件。 Here's the NSFileManager reference documentation about -replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:

    【讨论】:

    • 很好的答案。赞成。如果文件同时以某种方式被处理,比如通过 UIDocumentInteractionController 发送到另一个应用程序,或者通过 AirPrint 打印,或者上传到服务器,该怎么办?这是否确保第二个应用程序(或打印机或服务器)将获得新文件或旧文件,而不是由新文件中的一些位和旧文件中的一些位组成的损坏文件?还是做出这样的假设不安全?
    • @KartickVaddadi 此方法保证出现在 target 位置的文件以原子方式放置在那里。我假设对 source 数据的并发修改会导致损坏。如果这是一个问题,您可能应该通过 NSFileCoordinator / NSFilePresenter API 包装对源文件的操作——至少在 iOS 上,您不应该从系统中获得不是 NSFileCoordinator 证明的修改。
    • 我说的是对目标的更改,而不是对源的更改。我理解(比如说)系统断电的原子性,但是如果你有一个文件句柄/流打开到目标文件怎么办?原子性如何影响它?假设您正在将“a.pdf”上传到服务器,并且在上传中途,文件被“b.pdf”替换。服务器是否有 a.pdf、b.pdf 或损坏的文件?同样用于打印或共享到另一个应用程序。如果答案是它会损坏,是否应该将目标上的所有操作都包含在文件协调中?
    • 我很确定这通过替换文件系统上的 inode 至少在 HFS+ 上有效,这可能意味着尝试写入在发生 inode 替换之前打开的文件句柄将开始失败文件写入系统调用(
    【解决方案2】:

    在这种情况下,您需要进行原子保存,最好使用NSDataNSStringwriteToFile:atomically: 方法(及其变体)来实现:

    NSData *myData = ...; //fetched from somewhere
    [myData writeToFile:targetPath atomically:YES];
    

    或者NSString

    NSString *myString = ...;
    NSError *err = nil;
    [myString writeToFile:targetPath atomically:YES encoding:NSUTF8StringEncoding error:&err];
    if(err != nil) {
      //we have an error.
    }
    

    【讨论】:

    • 如果您不想将整个文件加载到 RAM 中(因为它很大)怎么办?加载文件似乎效率低下。
    • @NickForge 你可以使用NSFileManager-moveItemAtPath:toPath:error:方法。
    • @JacobRelkin 此方法不会覆盖现有文件,因此无法使用。
    • 同意,但它是原子的吗?
    【解决方案3】:

    如果您不确定文件是否存在,这适用于 swift 3+

    try? FileManager.default.removeItem(at: item_destination)
    try FileManager.default.copyItem(at: item, to: item_destination)
    

    如果文件不存在,第一行将失败并被忽略。如果在第二行出现异常,它会按应有的方式抛出。

    【讨论】:

    • 谢谢。替换为我删除了源项目。有了你的建议,效果很好
    【解决方案4】:

    Swift4:

    _ = try FileManager.default.replaceItemAt(previousItemUrl, withItemAt: currentItemUrl)
    

    【讨论】:

    • 虽然此代码可能会回答问题,但提供有关 如何 和/或 为什么 解决问题的附加上下文将改善答案的长期性价值。
    • 添加上下文,因为这是页面上的最佳答案,如果文件存在,它将替换文件,如果不存在,则将文件保存在那里。它返回新项目的 url 或 nil。
    • 嗯,如果你正在做 swift 但 OP 没有在 swift 中编码,这可能是最好的答案。
    【解决方案5】:

    检测文件存在错误,删除目标文件并重新复制。

    Swift 2.0 中的示例代码:

    class MainWindowController: NSFileManagerDelegate {
    
        let fileManager = NSFileManager()
    
        override func windowDidLoad() {
            super.windowDidLoad()
            fileManager.delegate = self
            do {
                try fileManager.copyItemAtPath(srcPath, toPath: dstPath)
            } catch {
                print("File already exists at \'\(srcPath)\':\n\((error as NSError).description)")
            }
        }
    
        func fileManager(fileManager: NSFileManager, shouldProceedAfterError error: NSError, copyingItemAtPath srcPath: String, toPath dstPath: String) -> Bool {
            if error.code == NSFileWriteFileExistsError {
                do {
                    try fileManager.removeItemAtPath(dstPath)
                    print("Existing file deleted.")
                } catch {
                    print("Failed to delete existing file:\n\((error as NSError).description)")
                }
                do {
                    try fileManager.copyItemAtPath(srcPath, toPath: dstPath)
                    print("File saved.")
                } catch {
                    print("File not saved:\n\((error as NSError).description)")
                }
                return true
            } else {
                return false
            }
        }
    }
    

    【讨论】:

    • 在 Swift 2.0 中,更好的选择是使用 do/try/catch 语法而不是 "try!"并使用委托。
    【解决方案6】:

    对于覆盖文件,我更喜欢

    NSData *imgDta = UIImageJPEGRepresentation(tImg, 1.0);
    
    [imgDta writeToFile:targetPath options:NSDataWritingFileProtectionNone error:&err];
    

    在循环中删除和复制文件有时无法按预期工作

    【讨论】:

      【解决方案7】:

      我认为您正在寻找的是 NSFileManagerDelegate 协议方法:

      - (BOOL)fileManager:(NSFileManager *)fileManager shouldProceedAfterError:(NSError *)error copyingItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath;
      

      通过这种方法,您可以决定如何处理现有文件(重命名/删除),然后继续进行复制。

      【讨论】:

        【解决方案8】:

        我认为您提到的纳秒的可能性很小。所以坚持第一种方法,删除现有文件并复制新文件。

        【讨论】:

        • 我相信纳秒取决于写入磁盘的数据大小;)
        【解决方案9】:

        这是为了改进问题“Move file and override [duplicate]”的“Swift 3 及以上”,该问题被标记为与该问题重复。

        将文件从 sourcepath(string) 移动到 DestinationPath(string)。 如果 DestinationPath 中已存在同名文件,则删除现有文件。

        // Set the correct path in string in 'let' variables.
        let destinationStringPath = ""
        let sourceStringPath = ""
        
        let fileManager:FileManager = FileManager.default
        do
        {
            try fileManager.removeItem(atPath: sourceStringPath)
        }
        catch
        {
        }
        
        do
        {
            try fileManager.moveItem(atPath: sourceStringPath, toPath: destinationStringPath)
        }
        catch
        {
        }
        

        【讨论】:

          猜你喜欢
          • 2014-01-08
          • 2013-10-03
          • 2014-08-13
          • 1970-01-01
          • 2015-01-20
          • 1970-01-01
          • 2023-03-29
          • 2019-05-30
          • 1970-01-01
          相关资源
          最近更新 更多