10

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

当保存一个只修改了一小部分页面的大型文档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 的堆栈跟踪的可爱图片:

UIDocument 慢写堆栈跟踪

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


编辑:

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


编辑:

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

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

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

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

4

2 回答 2

5

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

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

这是我的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

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

于 2013-03-12T02:46:53.047 回答
3

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

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

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

于 2019-10-16T03:06:58.450 回答