UIDocument和NSFileWrapper – 尽管有增量更改,但大文件需要很长时间才能保存

我有一个基于UIDocument的应用程序,它使用NSFileWrapper来存储数据。 ‘master’文件包装器包含许多其他目录文件包装器,每个包装器代表文档的不同页面。

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

我的contentsForType:error: override返回一个新的目录文件包装器,其中包含主文件包装器的内容(例如, WWDC 2012会话218 – 使用带有UIDocument的iCloud ):

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

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

UIDocument写入堆栈跟踪速度慢

顺便提一下,它表示要保存的工作线程中的~1.6s – 在实际运行时间内这相当于大约8秒。


编辑:

有什么方法可以检查文件包装器是否需要写入磁盘? 正因如此,我可以确认我做某些奇怪的事情,例如在我做一个小改动时更新每个子文件包装器(尽管我确定我不是……)。


编辑:

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

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

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

但我仍然想弄清楚为什么这不会发生在我的应用程序中。 对于大型多页文档(大型,但仍然比我上面执行的CloudNotes测试的文档小很多倍),用户可以等待很多秒才能关闭文档。 我不想把旋转器放在一个应该是瞬间的东西上。

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 

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