Ios UID文件及;NSFileWrapper-在保存过程中更改文件包装时NSFastEnumerationMutationHandler

Ios UID文件及;NSFileWrapper-在保存过程中更改文件包装时NSFastEnumerationMutationHandler,ios,thread-safety,save,uidocument,nsfilewrapper,Ios,Thread Safety,Save,Uidocument,Nsfilewrapper,我有一个基于UIDocument的应用程序,它使用NSFileWrappers来存储数据。“主”文件包装包含许多额外的目录文件包装,每个包装代表文档的不同页面 每当我在UIDocument保存时更改文档时(在writeContents:andAttributes:safelytoul:forSaveOperation:error:),应用程序就会崩溃。以下是堆栈跟踪: 很明显,我正在修改与UIDocument在后台枚举的文件包装器相同的实例。实际上,我检查了在contentsForType:e

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

每当我在
UIDocument
保存时更改文档时(在
writeContents:andAttributes:safelytoul:forSaveOperation:error:
),应用程序就会崩溃。以下是堆栈跟踪:

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

- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
    if (!_fileWrapper) {
        [self setupEmptyDocument];
    }
    return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}
这是实施该方法的认可方法(根据)

因此,我想问题是:这种方法如何是线程安全的?


当主文件包装器的
fileWrappers
本身就是目录文件包装器时,情况会有所不同吗?如果认可的方法是错误的,应该如何做?

如果您正在调用任何
writeContents:…
方法,则不应该这样做。您应该调用
saveToURL:forSaveOperation:completionHandler:
writeContents:…
方法用于高级子类化

UIDocument
使用两个线程-主线程和“UIDocument File Access”线程(如果您将更多的
UIDocument
子类化,则可以通过
执行同步文件访问,使用block:
)在中执行操作)

使用
UIDocument
的线程安全性与Objective C中的任何内容类似-只允许拥有对象的线程修改它。如果要更改的对象正在读取,请在写入完成后将其排队以进行更改。也许可以更改您的
UIDocument
子类拥有的另一个对象,并将它们拉入
contentsForType中的新
NSFileWrapper
。传递文件包装器的副本
NSDictionary

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

保存时,您通常希望让
UIDocument
决定何时执行此操作,并通过
updateChangeCount:
方法(参数为
UIDocumentChangeDone
)。如果要立即保存某些内容,请使用
saveToURL:forSaveOperation:completionHandler:
方法


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

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

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

另一个技巧是,您还需要复制子目录的NSFileWrapper的文件名、fileAttributes(可能还有preferredFilename),以便进行增量保存


HTH.

我没有遇到过这种情况,但似乎NSFileCoordinator可以完成这项工作?@MikeM你可能是对的,它可以防止崩溃,但我担心它可能会真正减慢速度。应用程序中的更新通常很小且频繁,并且需要最新的内容才能保持响应。我我必须进一步研究这种方法,看看它是否可行。但是问题仍然存在-受制裁的UIDocument使用方法是否不线程安全?谢谢你的回答。我已经理解了你提到的大部分内容(但很高兴在这里简洁地说明)例如,我正在重写
writeContents:…
并调用其超级实现以实现预览保存,我正在使用
updateChangeCount:
标记所需的保存。我还知道
UIDocument
处理文件协调,但在根文件包装器上进行协调写入并不意味着协调子文件上的ion?来自《文件系统编程指南》:“注意:当将
NSFileWrapper
实例指定为协调项时,文件包装中的所有文件……都将自动成为该文件协调的一部分。”。我当前使用单独的数据对象来存储数据(在我的
UIDocument
子类拥有的
Page
实例中),但一旦做了更改,我就在根文件包装下放置一个新的文件包装,就像在苹果的Clo中一样