【问题标题】:UIDocument & NSFileWrapper - large files taking a long time to save, despite incremental changesUIDocument 和 NSFileWrapper - 大文件需要很长时间才能保存,尽管有增量更改
【发布时间】:2013-02-20 06:26:20
【问题描述】:

我有一个基于UIDocument 的应用程序,它使用NSFileWrappers 来存储数据。 “主”文件包装器包含许多额外的目录文件包装器,每个包装器代表文档的不同页面。

当保存一个只有一小部分页面被修改的大文档时,UIDocument 会在后台花费很长时间来编写更改(在writeContents:andAttributes:safelyToURL:forSaveOperation:error: 中)。当然,它应该只写出对文件包装器的这一小改动……什么花了这么长时间?

我的contentsForType:error: 覆盖返回一个新的目录文件包装器,其中包含主文件包装器的内容(à la WWDC 2012 Session 218 - Using iCloud with UIDocument):

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
    if (!_fileWrapper) {
        [self setupEmptyDocument];
    }
    return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}

这是来自 Time Profiler 的堆栈跟踪的可爱图片:

顺便说一句,它说要在该工作线程中保存约 1.6 秒 - 在实际运行时间中,这相当于大约 8 秒。


编辑:

有什么方法可以检查文件包装器是否需要写入磁盘?这样我就可以确认,当我做一个小改动时,我并没有做一些奇怪的事情,比如更新每个子文件包装器(尽管我确定我不是......)。


编辑:

我进一步尝试了 CloudNotes 示例应用程序,似乎NSFileWrapper 确实实现了增量保存,至少在这种情况下!我通过初始化一个包含 100 个注释的文档来测试它,每个注释包含大约 5MB 的数据。我在这里和那里做了一个小编辑(文本视图的单个字符更改将文档标记为需要保存),并大致记录了每次保存所用的时间。测试比较粗糙(在模拟器上运行),但结果是这样的:

  • 第一次写入:~8000ms
  • 第二次写入:~4000ms
  • 第三次写入:~300ms
  • 所有后续写入:~40ms

显然有很多因素会影响它所花费的时间,特别是因为它是在后台线程中使用文件协调来保存的,但总的来说,趋势似乎总是这种指数衰减,直到所有写入都变得非常快。

但我仍在试图弄清楚为什么这不会在我的应用程序中发生。对于一个大的多页文档(很大,但仍然比我上面执行的 CloudNotes 测试的文档小很多倍),用户可能要等待很多秒才能关闭文档。我不想为应该几乎是即时的事情设置微调器。

【问题讨论】:

  • 您是在进行自动保存,还是一次保存所有内容?您是否从主线程启动保存?你是替换每一个 NSFileWrapper,还是只替换那些实际改变的?
  • 我完全依赖自动保存,所以保存是由 UIDocument 启动的,通常在写入(后台线程)之前调用contentsForType:error:(主线程)。我只是替换我已更改的文件包装器(它们更改时)。但是,我注意到我正在使用一种方法来查看我的所有页面文件包装器并将它们排序为有序数组。我认为这弄乱了 NSFileWrapper 的延迟加载并导致它们需要以某种方式编写。我只是在实现一个索引,这样我就不必这样做了,并且会看到它有什么不同。
  • 不,更改为仅使用索引文件来跟踪页面/页码并没有帮助。仍在尝试找出可能导致保存缓慢的原因。
  • 你可以加入我的chat here
  • 你有没有发现你到底错过了什么?

标签: ios save uidocument nsfilewrapper


【解决方案1】:

我知道这是一个非常古老的线程,但为了帮助未来的旅行者:在我的例子中,我有一个子目录 NSFileWrapper,它没有增量保存。

我发现,如果您制作 NSFileWrapper 的副本,您需要将副本的文件名、文件属性(可能还有首选文件名)设置为原始文件名,以便增量保存。复制这些内容后,子文件夹的内容将逐步保存(即仅在替换为新的 NSFileWrappers 时才写入)。

Apple 注意:说真的,整个 NSFileWrapper API 是一团糟,应该清理干净。

【讨论】:

  • 在(也许)“捆绑设计指南”中有一个超级简短的提及“在包中使用平面结构”。是否可以将图像放在目录中,而不是“平面”将它们直接放在包中,这是不增量保存的原因之一吗?
【解决方案2】:

NSFileWrapper 实际上是将整个文档加载到内存中。因此,使用UIDocument,使用NSFileWrapper 实际上并不适合大型文档。文档让您认为它会进行增量保存,但在我的情况下,它似乎没有这样做。

UIDocument 不仅限于NSFileWrapperNSData。你可以使用你自己的自定义类,你只需要重写某些方法。我最终编写了自己的文件包装类,它只是引用磁盘上的文件并按需读取/写入单个文件。

这是我的UIDocument 类使用自定义文件包装器的样子:

@implementation LSDocument

- (BOOL)writeContents:(LSFileWrapper *)contents
        andAttributes:(NSDictionary *)additionalFileAttributes
          safelyToURL:(NSURL *)url
     forSaveOperation:(UIDocumentSaveOperation)saveOperation
                error:(NSError *__autoreleasing *)outError
{
    return [contents writeUpdatesToURL:self.fileURL error:outError];
}

- (BOOL)readFromURL:(NSURL *)url error:(NSError *__autoreleasing *)outError
{
    __block LSFileWrapper *wrapper = [[LSFileWrapper alloc] initWithURL:url isDirectory:NO];
    __block BOOL result;
    dispatch_sync(dispatch_get_main_queue(), ^(void) {
        result = [self loadFromContents:wrapper
                                 ofType:self.fileType
                                  error:outError];
    });
    [wrapper loadCache];
    return result;
}

@end

我将其用作基类并将其子类化用于其他项目。它应该让您了解如何集成自定义文件包装器类。

【讨论】:

  • 感谢您花时间回答(我的两个相关问题!)。我花了很多时间试图研究 NSFileWrapper 的细节,但是关于它们的信息太少了。许多事情让我感到困惑:1)如果文件包装器将所有数据加载到内存中,并且不支持增量保存,那么它们的意义何在? 2)文档不只是“让你觉得”他们做增量储蓄,它直接说!来自“iOS 的基于文档的应用程序编程指南”:“文件包装器支持增量保存。与单个二进制数据对象相比,如果文件...
  • ...wrapper 包含您的文档数据 - 例如,文本和图像 - 并且文本更改,只有包含文本的文件必须写入磁盘。此功能可带来更好的性能。”
  • 文件 NSFileWrapper 是不可变的,所以我认为它可以保存已替换的文件包装器。因此,如果您更换一个 NSFileWrapper,即使它已更改,它也可能会保存它。 NSFileWrapper 将所有内容加载到内存中,因此在我的情况下这是不可取的 - 所以我自己制作了引用文件而不是 BOOL 来表示我想在内存中缓存某些文件。当我更新文件时,它会设置一个带有“更新的”BOOL 的“内容”ivar。当我的父母被保存时,它会查找“更新”并保存更新的文件,如果“缓存”BOOL 为假,则将“内容”设置为 nil。
  • 您的实现听起来不错,但我只想在 NSFileWrapper 之外作为最后的手段。我的理解是自定义增量读/写解决方案主要针对文档包含数据块(例如视频文件)的情况。我的文档由许多非常小的文件组成,即使文档总大小> 100MB,我在阅读时也看不到任何性能问题 - 只是在写作时。这向我暗示了 some 种 NSFileWrapper 优化,但我无法通过研究来支持这一点。你有没有看到 NSFileWrapper 将所有内容加载到内存中的证据?
  • 我相信有一些选择,比如内存映射,但最终它就是这样工作的。文档并不完全清楚。我在这里开源了我的文件包装器:github.com/lukescott/LSFileWrapper
猜你喜欢
  • 2013-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多