【问题标题】:UIDocument & NSFileWrapper - NSFastEnumerationMutationHandler while changing file wrapper during a saveUIDocument 和 NSFileWrapper - NSFastEnumerationMutationHandler 在保存期间更改文件包装器
【发布时间】:2013-02-20 08:31:12
【问题描述】:

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

每当我在UIDocument 保存时(在writeContents:andAttributes:safelyToURL:forSaveOperation:error: 中)对文档进行更改,应用程序就会崩溃。这是堆栈跟踪:

很明显,我正在修改UIDocument 在后台枚举的文件包装器的同一实例。事实上,我检查了当返回 contentsForType:error: 中的数据模型的快照时,返回的子文件包装器指向的对象与数据模型中当前驻留(和正在编辑)的对象相同,而不是副本。

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

这是实施此方法的认可方法(根据WWDC 2012 Session 218 - Using iCloud with UIDocument)。

所以我想问题是:这种方法如何是线程安全的?

当主文件包装器的fileWrappers 本身是目录文件包装器时,情况是否有所不同?如果批准的方法是错误的,应该怎么做?

【问题讨论】:

  • 我还没有遇到过这种情况,但似乎 NSFileCoordinator 可以完成这项工作?
  • @MikeM 你可能是对的,因为它可以防止崩溃,但我担心它有可能真正减慢速度。应用程序中的更新通常很小且频繁,应用程序需要最新的内容才能保持响应。我将不得不进一步研究这种方法,看看它是否可行。但是问题仍然存在 - UIDocument 使用的认可方法不是线程安全的吗?

标签: ios thread-safety save uidocument nsfilewrapper


【解决方案1】:

如果您正在调用任何writeContents:... 方法,则不应如此。你应该打电话给saveToURL:forSaveOperation:completionHandler:writeContents:... 方法用于高级子类化。

UIDocument 使用两个线程 - 主线程和“UIDocument 文件访问”线程(如果您将更多UIDocument 子类化,您可以通过performAsynchronousFileAccessUsingBlock: 执行操作)。

UIDocument 的线程安全就像 Objective C 中的任何东西一样——只让拥有对象的线程修改它。如果您要更改的对象正在被读取,请在写入完成后将其排队等待更改。也许更改您的UIDocument 子类拥有的不同对象并将它们拉入contentsForType:error: 中的新NSFileWrapper。传递 fileWrappers NSDictionary 的副本。

NSFileWrapper 实际上将整个文档加载到内存中。 NSFileWrapper 实际上是在readFromURL:error: 方法中的“UIDocument 文件访问”线程中创建的,然后将其传递给loadFromContents:ofType:error: 方法。如果您的文档很大,这可能需要一段时间。

保存时,您通常希望让UIDocument 决定何时执行此操作,并通过updateChangeCount: 方法(参数为UIDocumentChangeDone)让它知道发生了一些变化。当你想现在保存一些东西时,你想使用saveToURL:forSaveOperation:completionHandler: 方法。

另外需要注意的是UIDocument 实现了NSFilePresenter 协议,该协议定义了NSFileCoordinator 使用的方法。 UIDocument 仅协调写入根文档,而不是子文件。您可能认为在文档中协调子文件可能会有所帮助,但您遇到的崩溃与字典在迭代时发生变异有关,因此这无济于事。如果您 (1) 想要获得文件更改通知,或者 (2) 另一个对象或应用程序正在读取/写入同一个文件,您只需要担心编写自己的 NSFilePresenterUIDocument 已经做了什么可以正常工作。但是,您确实希望在移动/删除整个文档时使用NSFileCoordinator

【讨论】:

  • 感谢您的回复。我已经理解了你提到的大部分内容(但很高兴在这里简明扼要地说明),例如我正在覆盖writeContents:... 并调用其超级实现以实现预览保存,并且我正在使用updateChangeCount: 来标记所需的保存。我也知道UIDocument 处理文件协调,但是在根文件包装器上的协调写入并不意味着对子文件的协调?来自文件系统编程指南:“注意:当 NSFileWrapper 实例被指定为协调项时,所有文件...
  • ...在文件包装器中自动成为该文件协调的一部分。"。我目前正在使用单独的数据对象来存储数据(在我的UIDocument 拥有的Page 实例中子类),但是一旦进行更改,我将在根文件包装器下放置一个新的文件包装器,就像在 Apple 的 CloudNotes 示例应用程序中所做的那样,而不是等待并将其添加到 contentsForType: 中。正如您所指出的,这肯定是问题所在。我将推迟对根文件包装器的更新,直到UIDocument 要求提供快照,然后查看是否一切正常。再次感谢。
  • 文档令人困惑,我遇到了和你一样的问题。所以我想我会尽可能多地报道。您可能想在其他地方进行预览保存。 CloudNotes 做了一些古怪的事情——它保存了第二个 UIDocument 以供预览。如果您并不总是保留每个文档的本地副本,您实际上只需要这样做。是的,如果您协调根文档,它可以应用于子文件,但是据我所知,这是假设其他应用程序/对象协调根文档(iCloud 和 UIDocument 这样做)。
  • 如果您需要澄清与 UIDocument 相关的任何内容,请告诉我。我花了很多时间弄清楚这些东西。
  • 非常感谢。我当然明白为什么要花很多时间!
【解决方案2】:

我知道这是一个古老的线程,但我最近遇到了这个问题,为了帮助未来的旅行者:如果你的主文件包装器中有子目录,你还需要复制那些 NSFileWrapper (除了复制根NSFileWrapper 同上)。

否则,当 UIDocument 后台线程在保存时枚举它们并且同时在主线程上发生修改时,可能会发生崩溃。目前尚不清楚,但这可能是 OP 遇到的问题。

另一个提示是,您还需要复制子目录的 NSFileWrapper 的文件名、文件属性(可能还有首选文件名),以便增量保存工作。

HTH。

【讨论】:

    猜你喜欢
    • 2013-02-20
    • 2012-06-14
    • 1970-01-01
    • 2014-06-09
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 2014-04-15
    • 1970-01-01
    相关资源
    最近更新 更多