Iphone 同时可靠地使用核心数据

Iphone 同时可靠地使用核心数据,iphone,ios,cocoa-touch,core-data,nsmanagedobjectcontext,Iphone,Ios,Cocoa Touch,Core Data,Nsmanagedobjectcontext,我正在构建我的第一个iOS应用程序,理论上应该非常简单,但我很难让它足够防弹,让我有信心将它提交到应用商店 简单地说,主屏幕有一个表视图,在选择一行时,它将转到另一个表视图,该表视图以主-详细方式显示与所选行相关的信息。底层数据作为JSON数据每天从web服务检索一次,然后缓存在核心数据存储中。删除当天之前的数据以阻止SQLite数据库文件无限增长。所有数据持久化操作都是使用核心数据执行的,细节表视图的基础是nsfetchedresultscoontroller 我看到的问题是,如果在检索、解析

我正在构建我的第一个iOS应用程序,理论上应该非常简单,但我很难让它足够防弹,让我有信心将它提交到应用商店

简单地说,主屏幕有一个表视图,在选择一行时,它将转到另一个表视图,该表视图以主-详细方式显示与所选行相关的信息。底层数据作为JSON数据每天从web服务检索一次,然后缓存在核心数据存储中。删除当天之前的数据以阻止SQLite数据库文件无限增长。所有数据持久化操作都是使用核心数据执行的,细节表视图的基础是
nsfetchedresultscoontroller

我看到的问题是,如果在检索、解析和保存新数据时,在主屏幕和详细屏幕之间快速切换多次,应用程序将完全冻结或崩溃。似乎存在某种竞争条件,可能是由于核心数据在后台导入数据,而主线程正在尝试执行获取,但我在猜测。我很难捕捉到任何有意义的崩溃信息,通常是核心数据堆栈深处的SIGSEGV

下表显示了加载详图表视图控制器时发生的事件的实际顺序:

Main Thread Background Thread viewDidLoad Get JSON data (using AFNetworking) Create child NSManagedObjectContext (MOC) Parse JSON data Insert managed objects in child MOC Save child MOC Post import completion notification Receive import completion notification Save parent MOC Perform fetch and reload table view Delete old managed objects in child MOC Save child MOC Post deletion completion notification Receive deletion completion notification Save parent MOC 导入器对象观察自己的MOC
NSManagedObjectContextDidSaveNotification
,然后发布自己的通知,该通知由详细信息表视图控制器观察。发布此通知后,表视图控制器将在其自身(父级)MOC上执行保存

我使用与“deleter”对象相同的基本模式,在导入当天的新数据后删除旧数据。这是在fetched results controller提取新数据并重新加载detail table视图后异步发生的


我没有做的一件事是观察任何合并通知或锁定任何托管对象上下文或持久存储协调器。这是我应该做的吗?我有点不确定如何正确地构建这一切,因此希望您能提供任何建议。

只是一个架构想法:

使用您声明的数据刷新模式(每天一次,删除和添加数据的整个周期),我实际上会每天创建一个新的持久存储(即以日历日期命名),然后在完成通知中,让表视图设置一个与新存储关联的新fetchedresultscontroller(可能是一个新的MOC),并使用它进行刷新。然后应用程序可以(在其他地方,可能也由该通知触发)完全销毁“旧”数据存储。此技术将更新处理与应用程序当前使用的数据存储和“切换”分离对新数据的访问可能会被认为是非常原子化的,因为发生的更改只是开始指向新数据,而不是希望在写入新数据(但尚未完成)时没有捕获到处于不一致状态的存储

显然,我遗漏了一些细节,但我倾向于认为,在使用过程中发生更改的许多数据都应该重新设计,以降低您所经历的崩溃的可能性


很高兴进一步讨论…

在iOS 5之前,我们通常有两个
NSManagedObjectContext
:一个用于主线程,一个用于后台线程。后台线程可以加载或删除数据,然后保存。然后传递产生的
NSManagedObjectContextDidSaveNotification
我们从ManagedObjectContextDidSaveNotification:调用了
mergeChangesFromManagedObjectContextDidSaveNotification:
,将这些内容引入到主线程上下文中。这对我们来说非常有效

其中一个重要方面是后台线程上的
save:
阻塞,直到主线程上的
mergeChanges fromManagedObjectContextDidSaveNotification:
完成运行之后(因为我们将mergeChanges…从侦听器调用到该通知)。这样可以确保主线程管理的对象上下文看到这些更改。我不知道如果您有父子关系,是否需要这样做,但您在旧模型中这样做是为了避免各种麻烦

我不确定在这两个上下文之间建立父子关系有什么好处。从您的描述来看,最终保存到磁盘的操作似乎发生在主线程上,出于性能原因,这可能并不理想。(特别是当您可能要删除大量数据时;在我们的应用程序中删除数据的主要成本总是发生在最终保存到磁盘期间。)


当控制器出现/消失时,您正在运行哪些代码,这可能会导致核心数据出现问题?您看到的是哪种类型的堆栈跟踪崩溃?

我遇到的多线程核心数据的主要问题是无意中访问线程/队列中的托管对象,而不是创建它的线程/队列中的托管对象

我发现一个很好的调试工具是addnsassert,它可以检查在主托管对象上下文中创建的to托管对象是否只在那里使用,而在后台上下文中创建的对象是否不在主上下文中使用

这将涉及子类化NSManagedObject上下文和NSManagedObject:

  • 将iVar添加到MOC子类中,并将创建它的队列分配给它
  • MO子类应检查当前队列是否与其MOC的队列属性相同

这只是几行代码,但长期使用可以防止您犯下其他情况下难以追踪的错误。

NSFetchedResultsController
已被证明对大规模删除有点敏感,因此我将首先从这里开始挖掘

我最初的问题是,tableview的重新获取和重新加载与delete操作的开始有什么关系
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [child setParentContext:self.managedObjectContext];        
    [child performBlock:^{
        // Create importer instance, passing it the child MOC...
    }];