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

Stu*_*art 8 save nsfilewrapper ios uidocument

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

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

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

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

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

UIDocument写入堆栈跟踪速度慢

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


编辑:

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


编辑:

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

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

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

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

Luk*_*uke 5

NSFileWrapper实际上是将整个文档加载到内存中。因此UIDocument,对于大文档来说,使用 anNSFileWrapper实际上并不好。该文档让您认为它可以进行增量保存,但就我而言,它似乎并没有这样做。

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
Run Code Online (Sandbox Code Playgroud)

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

  • 我相信有一些选择,例如内存映射,但最终这就是它的工作原理。文档并不完全清楚。我在这里开源了我的文件包装器:https://github.com/lukescott/LSFileWrapper (3认同)