我有一个基于UIDocument
的应用,它使用NSFileWrapper
来存储数据。 'master'文件包装器包含许多其他目录文件包装器,每个包装器代表文档的不同页面。
每当我在UIDocument
保存时(writeContents:andAttributes:safelyToURL:forSaveOperation:error:
)对文档进行更改,应用程序崩溃。这是堆栈跟踪:
我似乎很清楚我正在修改UIDocument
在后台枚举的文件包装器的同一个实例。实际上,我检查了在contentsForType:error:
中返回数据模型的快照时,返回的子文件包装器指向与数据模型中当前驻留(和正在编辑)的对象相同的对象,而不是副本。 / p>
- (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
本身是目录文件包装器时,情况是否有所不同?如果认可的方法是错误的,应该如何呢?
答案 0 :(得分:6)
如果您正在调用任何writeContents:...
方法,则不应该这样做。你应该打电话给saveToURL:forSaveOperation:completionHandler:
。 writeContents:...
方法适用于高级子类。
UIDocument
使用两个线程 - 主线程和“UIDocument文件访问”线程(如果你继承了UIDocument
的更多内容,你可以在via performAsynchronousFileAccessUsingBlock:
中执行操作)。
UIDocument
的线程安全与Objective C中的任何内容类似 - 只允许拥有对象的线程修改它。如果正在读取要更改的对象,请在写入完成后将其排队以进行更改。也许更改您的UIDocument
子类所拥有的其他对象,并将其拉入NSFileWrapper
中的新contentsForType:error:
。传递fileWrappers NSDictionary
的副本。
NSFileWrapper
实际上将整个文档加载到内存中。 NSFileWrapper
实际上是在readFromURL:error:
方法的“UIDocument File Access”线程中创建的,然后传递给loadFromContents:ofType:error:
方法。如果你有一个大文件,这可能需要一段时间。
保存时,您通常希望UIDocument
决定何时执行此操作,并通过updateChangeCount:
方法(param为UIDocumentChangeDone
)让其知道某些内容已发生变化。如果您想立即保存 ,则需要使用saveToURL:forSaveOperation:completionHandler:
方法。
另外需要注意的是UIDocument
实现了NSFilePresenter
协议,该协议定义了NSFileCoordinator
使用的方法。 UIDocument
仅在根文档上协调写入,而不是子文件。您可能认为在文档中协调子文件可能会有所帮助,但是您获得的崩溃与在迭代字典时改变字典有关,因此无法提供帮助。如果您(1)想要获得文件更改通知,或(2)另一个对象或应用正在读/写同一文件,您只需要担心编写自己的NSFilePresenter
。 UIDocument
已经完成的工作正常。但是,您确实希望在移动/删除整个文档时使用NSFileCoordinator
。
答案 1 :(得分:0)
我知道这是一个古老的话题,但是最近我遇到了这个问题,并为将来的旅行者提供了帮助:如果主文件包装器中有子目录,则还需要复制那些NSFileWrappers(除了复制根目录之外)如上所述的NSFileWrapper。
否则,当UIDocument后台线程在保存时枚举它们并在主线程上同时进行修改时,会发生崩溃。尚不清楚,但这可能是OP遇到的问题。
另一个提示是,您还需要复制子目录的NSFileWrapper的文件名,fileAttributes(以及可能的preferredFilename),以便进行增量保存。
HTH。