我遇到了一个令人烦恼的核心数据错误,这个错误导致我的一些最终用户遇到了一些问题。一些背景:
我的数据模型包含两种配置,即CRM配置和目录配置。 CRM配置具有与诸如其他相关实体之间的联系人/帐户之类的事物相关的实体。目录中还有其他实体,例如Collections / Products / etc.
用户首次登录后,他们被迫通过Web服务同步两个商店。这一切都很好。在应用程序的最新更新中,我提示用户他们需要再次同步他们的目录数据。这会将它们发送回同步屏幕,以便它们可以再次同步目录。
这就是问题所在。当他们去执行同步时,我们的代码将从上下文中删除持久存储,删除.sqlite文件,然后将存储重新添加到持久存储中。
代码:
- (void) resetStoreAtPath:(NSURL *)path withConfiguration:(NSString *)configuration
{
NSPersistentStoreCoordinator *coordinator = self.persistentStoreCoordinator;
//Get the store for this database.
NSPersistentStore *store = [coordinator persistentStoreForURL:path];
NSError *__autoreleasing error = nil;
if(store != nil)
{
if(![coordinator removePersistentStore:store error:&error])
{
//Log The Error
}
}
//Remove the file. Even if it errors, try to add it, but log it.
error = nil;
if(![[NSFileManager defaultManager] removeItemAtURL:path error:&error])
{
//Log the error
}
error = nil;
[self addStoreWithConfiguration:configuration URL:path error:&error];
//Log Error
}
以上代码错误都没有。使用iFunBox,我甚至可以浏览应用程序并检查文件系统,并且重新创建.sqlite文件没有任何问题。
完成后,我们再次同步目录数据。这里奇怪的是,偶尔(并非每次都是),Core Data将决定将Catalog配置实体放入CRM db而不是新的Catalog db!
然后在应用程序中,当用户去浏览目录时,目录数据不再显示。
我已将.sqlite文件从设备中删除,以确认目录数据位于crm.sqlite数据库中。并且catalog.sqlite db没有任何数据行。这有什么特别的原因吗?有更好的方法吗?
编辑:这是另一种读取它的方法。
- (NSPersistentStore *) addStoreWithConfiguration:(NSString *)configuration URL:(NSURL *)url error:(NSError **)error
{
NSDictionary *options = @{ NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}, NSMigratePersistentStoresAutomaticallyOption: @YES, NSInferMappingModelAutomaticallyOption: @YES };
NSPersistentStore *store = [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:configuration URL:url options:options error:error];
if(store == nil)
{
//Something went wrong. Log it. Remove File, try to readd.
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
store = [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:configuration URL:url options:options error:error];
if(store == nil)
{
//Something is dreadfully wrong, log and throw.
[NSException raise:@"NSInternalInconsistencyException" message:@"After two attempts, we were unable to create the database. User may need to uninstall their app to continue." parameters:nil];
}
}
return store;
}
我只删除了我需要的商店,在本例中是Catalog商店。
编辑2:关于您的问题......
删除代码发生在主线程上以响应按下按钮。然后我们开始下载zip文件的操作,一旦完成,我们在后台操作中处理zip文件。后台进程使用私有队列上下文,一旦保存就会在主上下文中合并。
我不认为在这种情况下锁定会有所帮助,因为在商店重置之后,目录的数据会在稍后保存。尽管如此,这并没有什么坏处。
也许描述问题如何发生可能会有所启发。
我可以通过以下方式防止这种情况发生:
我想知道在这一点上,整个核心数据堆栈的重新创建将解决这个问题。我真的想避免这种情况,因为它似乎完全没必要。
答案 0 :(得分:3)
iOS 7下是否发生此错误?如果是这样,您遇到了日志文件的问题。 Apple在iOS 7的Core Data下更改了SQLite的配置以使用日志文件。如果您要删除sqlite文件,那么您还需要检查并删除日志文件。它们将具有与主sqlite文件相同的核心文件名。
另一种选择是关闭日记功能,以便获得与iOS 7之前相同的行为。
日志文件是否仍在磁盘上,或者您是否在干净的环境中获得此行为?
您的-addStore...
方法是什么样的?
你是否同时放下这两家商店?
您的drop存储代码是什么样的?
您似乎没有检查从-addPersistentStore...
返回的错误。你收到错误了吗?我可以很容易地看到其中一个-addPersistentStore...
调用失败然后磁盘上只有一个存储的情况,因此Core Data会将数据写入其中。
建议您始终检查错误,即使只是将其记录到磁盘。我会更进一步,建议商店回来的NSAssert
!nil
。
消除其余部分,我的思绪转向线程化。你什么时候删除这家商店?是否有可能在删除和添加之间触发保存?
这是在后台线程上开火吗?
如果您将NSPersistentStoreCoordinator
锁定在此过程的顶部,然后在底部解锁,会发生什么?
使用配置时,Core Data实际上会将所有表放在所有存储中,即使这些表未被使用。如果我们遇到竞争条件,我可以看到Core Data可以写入“错误”商店的情况,因为在那个时刻它是唯一的商店。锁定PSC将测试该理论。
重新创建整个Core Data堆栈并不昂贵,您已经在做昂贵的部分了。这绝对闻起来像是在错误的时间与商店碰撞,但没有在我面前的应用程序很难证明/反驳。如果您的应用程序结构良好,足以允许重建堆栈,我同意这样做将是您的最短路径。在这种情况下,从轨道进行核武器是一种可行的策略。
答案 1 :(得分:0)
马库斯肯定让我走上了正确的道路,所以谢谢你。
最后,刷新整个核心数据堆栈解决了我的问题,尽管我有点困惑为什么这是必要的。
我肯定不得不说这是一个潜在的问题,持久存储协调器在使用多个配置并重置存储时没有将正确的实体插入正确的存储。