2013-03-05 60 views
5

我有一个基于UIDocument的应用程序,它使用NSFileWrapper来存储数据。 '主'文件包装器包含许多附加的目录文件包装器,每个包装器代表文档的不同页面。UIDocument&NSFileWrapper - NSFastEnumerationMutationHandler在保存期间更改文件包装

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

UIDocument crash stack trace

似乎很清楚,我修改了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本身是目录文件包装时,情况有什么不同吗?如果制裁的方法是错误的,应该如何做

+0

我还没有碰到这种情况,但它似乎是一个NSFileCoordinator可能做的工作吗? – 2013-03-05 19:31:36

+0

@MikeM你可能是对的,因为它可以防止崩溃,但我担心它有可能真的放慢速度。应用程序中的更新通常很少且频繁,因此应用程序需要最新的内容才能保持响应。我将不得不进一步调查这种方法,看看它是否可行。然而,问题仍然存在 - UIDocument使用不是线程安全的认可方法? – Stuart 2013-03-05 20:14:38

回答

6

如果您正在调用writeContents:...方法中的任何一种,则不应该这样做。您应该打电话给saveToURL:forSaveOperation:completionHandler:writeContents:...方法适用于高级子类。

UIDocument使用两个线程 - 主线程和“UIDocument File Access”线程(如果您子类更多UIDocument,则可以通过执行操作)。

线程安全与UIDocument就像Objective-C中的任何东西 - 只让拥有一个对象的线程修改它。如果要更改的对象正在读取,请在写入完成后对其进行排队以进行更改。也许更改你的UIDocument子类拥有的另一个对象,并将它们拉入contentsForType:error:中的新NSFileWrapper。传递一份fileWrappers NSDictionary的副本。

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

当你保存你通常想让UIDocument决定什么时候这样做,并让它知道通过updateChangeCount:方法(参数是UIDocumentChangeDone)已更改的东西。当你想节省现在你想要使用saveToURL:forSaveOperation:completionHandler:方法。

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

+0

感谢您的回复。我已经了解了你所提到的大部分内容(但在这里简洁地说明它很好),例如我重写'writeContents:...'并调用它的超级实现来实现预览保存,并且我使用'updateChangeCount:'来​​标记所需的保存。我也知道'UIDocument'处理文件协调,但是在根文件包装器上没有协调写入意味着对子文件的协调?从文件系统编程指南:“请注意:当一个'NSFileWrapper'实例被指定为项目协调,所有的文件... – Stuart 2013-03-12 11:20:49

+0

...文件包装中会自动将该文件的协调的一部分。”我目前正在使用一个单独的数据对象来存储数据(在''UIDocument'子类拥有的'Page'实例中),但是一旦发生更改,我会在根文件包装器中放置一个新的文件包装器是在Apple的CloudNotes示例应用程序中完成的,而不是等待并将其添加到'contentsForType:'中。正如你指出的那样,这肯定是问题所在。我会推迟更新到根文件包装,直到'UIDocument'请求一个快照,并看看是否一切正常。再次感谢。 – Stuart 2013-03-12 11:44:32

+0

文档很混乱,我遇到了问题。所以我想我会尽可能多地覆盖。您可能想要在其他地方进行预览。 CloudNotes做了一些古怪的事情 - 它为预览节省了第二个UIDocument。如果您并不总是保留每个文档的本地副本,那么您确实只需要这样做。是的,如果你协调根文件它可以应用于子文件,但据我所知,这是假设其他应用程序/对象协调根文件(iCloud&UIDocument这样做)。 – Luke 2013-03-12 15:31:27