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

时间:2013-03-05 17:57:32

标签: ios save uidocument nsfilewrapper

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

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

我的contentsForType:error:覆盖返回一个新的目录文件包装器,其中包含主文件包装器的内容(àlaWWDC 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的堆栈跟踪的可爱图片:

UIDocument slow write stack trace

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


修改

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


修改

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

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

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

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

2 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:0)

我知道这是一个非常古老的线程,但是可以帮助将来的旅行者:就我而言,我有一个子目录NSFileWrapper,它没有进行增量保存。

我发现,如果您制作NSFileWrapper的副本,则需要将副本的文件名,fileAttributes(以及可能的preferredFilename)设置为原始文件名,以便增量保存。复制完这些文件后,子文件夹的内容将以增量方式保存(即仅在被新的NSFileWrappers替换时才写入)。

Apple的注意事项:严重的是,整个NSFileWrapper API都是一团糟,应该清理。