重置CoreData持久存储

16
基本上,我想做的是清除CoreData持久存储中的所有数据,然后导入新数据。你会怎么做?似乎最简单的解决方案是调用[NSPersistentStoreCoordinator removePersistentStore:error:],然后删除文件。这是可用的最佳实践吗?它是线程安全的吗?
非常感谢,
#
问题0.1:是什么
我正在尝试更新CoreData持久存储中的数据。我的用户看到一个包含统计数据的表格视图。我想通过删除所有现有数据,然后导入新数据来更新应用程序。我希望显示进度视图,告诉用户应用程序没有挂起。
我已经在我的AppDelegate中添加了以下resetPersistentStore方法(persistentStoreCoordinator供参考):
// ...
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
// ...

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator != nil) {
        return persistentStoreCoordinator;
    }

    NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kPersistentStoreFilename]];

    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return persistentStoreCoordinator;
}

/**
 * Will remove the persistent store
 */
- (NSPersistentStoreCoordinator *)resetPersistentStore {
    NSError *error;

    [managedObjectContext lock];

    // FIXME: dirty. If there are many stores...
    NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] objectAtIndex:0];

    if (![persistentStoreCoordinator removePersistentStore:store error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

    // Delete file
    if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 

    // Delete the reference to non-existing store
    [persistentStoreCoordinator release];
    persistentStoreCoordinator = nil;

    NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
    [managedObjectContext unlock];

    return r;
}

在我的看法中,我会在另一个线程中执行此操作(因为我正在使用MBProgressHUD):

PatrimoineAppDelegate *appDelegate = (PatrimoineAppDelegate *)[[UIApplication sharedApplication] delegate]; 
// Delete everything
[appDelegate resetPersistentStore];

我遇到了一个EXC_BAD_ACCESS的错误...

我不太了解CoreData或多线程,可能会犯一些明显的错误...


我也非常想知道如何做到这一点。 - Simon Woodside
使用写前日志(WAL)是个不好的主意。磁盘上也会有一个wal和shm文件。 - kylef
7个回答

11

对于那些在 iOS 9 及以上版本中尝试此操作的用户,现在可以使用 destroyPersistentStoreAtURLreplacePersistentStoreAtURL API。


2
你知道有没有任何关于如何使用这些方法的信息?苹果的文档很少。 - SAHM
2
具体来说,由于文档没有说明,我不确定哪些选项适用。 - SAHM

8
如果您的目标是清空数据存储并重新加载新信息,则最好使用NSManagedObjectContext的reset,然后加载新数据。
来自NSManagedObjectContext文档

一个上下文总是有一个“父”持久性存储协调器,它提供模型并将请求分派到包含数据的各个持久性存储中。没有协调器,上下文无法完全功能。上下文的协调器提供托管对象模型并处理持久性。从外部存储中提取的所有对象都与全局标识符(NSManagedObjectID的实例)一起在上下文中注册,该标识符用于唯一标识每个对象对外部存储。

删除持久存储并使用与存储关联的托管对象上下文可能是错误的原因。

3
只是为了确保理解:我的数据已经保存在持久存储中了。一个简单的[moc reset](之后跟一个保存操作)会将sqlite文件中的所有内容都清除吗? - charlax
这解决了一个困扰我_day_的问题!谢谢!问题:如何更新被外部进程编辑过的表视图中的数据:在从fetchedResultsController请求新数据之前立即调用[myManagedObjectContext reset];。我很开心! - mpemburn
Charlax并没有明确表示重置后再保存确实会重置。 - user4951
1
@Giao,删除持久存储是否公平?还是说这是一个不良的实践?请给我建议。 - Exploring
2
就此而言,重置上下文并不会改变存储的内容。它只是擦除了加载到上下文中的任何内容。 - MiKL

5

仍然没法工作!由于与无效的持久化存储相关联,Managed Context Object引起了中止。最终,如果我删除Managed Context Object并让应用程序重新创建,它就能正常工作。

这是我的修改意见。

- (NSPersistentStoreCoordinator *)resetPersistentStore 
{
  NSError *error = nil;

  if ([persistentStoreCoordinator_ persistentStores] == nil)
    return [self persistentStoreCoordinator];

  [managedObjectContext_ release];
  managedObjectContext_ = nil;

  // FIXME: dirty. If there are many stores...
  NSPersistentStore *store = [[persistentStoreCoordinator_ persistentStores] lastObject];

  if (![persistentStoreCoordinator_ removePersistentStore:store error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
  }  

  // Delete file
  if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
    if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
      NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
      abort();
    } 
  }

  // Delete the reference to non-existing store
  [persistentStoreCoordinator_ release];
  persistentStoreCoordinator_ = nil;

  NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];

  return r;
}

这在我的情况下起了作用。之前,我手动重新创建持久存储,但是会抛出错误:对象的持久化存储不可从此NSManagedObjectContext的协调器访问。通过将持久存储和托管对象上下文分配为nil,您可以强制应用程序自行重新创建它,并消除此错误。谢谢! - Michael D.

4

以下是解决方案。可能还有更加优雅的选项(如锁定...),但此方法有效。

/**
 * Will remove the persistent store
 */
- (NSPersistentStoreCoordinator *)resetPersistentStore {
    NSError *error = nil;

    if ([persistentStoreCoordinator persistentStores] == nil)
        return [self persistentStoreCoordinator];

    [managedObjectContext reset];
    [managedObjectContext lock];

    // FIXME: dirty. If there are many stores...
    NSPersistentStore *store = [[persistentStoreCoordinator persistentStores] lastObject];

    if (![persistentStoreCoordinator removePersistentStore:store error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

    // Delete file
    if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
        if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }

    // Delete the reference to non-existing store
    [persistentStoreCoordinator release];
    persistentStoreCoordinator = nil;

    NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];
    [managedObjectContext unlock];

    return r;
}

7
这真的很丑陋且过于复杂。为什么不重新构建整个Core Data堆栈并将新上下文交给您的视图控制器? 更换NSManagedObjectContext下的PSC可能非常危险。其次,您永远不应该从多个线程中访问NSManagedObjectContext,堆栈被设计成每个线程创建一个上下文的原因是处理锁定正确。与其删除文件,您最好从存储中实际删除对象,然后重新加载它,或者重新构建整个堆栈。 - Marcus S. Zarra
3
谢谢Marcus,我不太确定听清楚你的意思;你会如何以广义来清除数据库中的所有数据?CoreData stack是什么意思?假设已经有一些数据存储在持久存储器中,我想删除所有数据然后导入新的数据。如果我理解苹果的文档,重置MOC并不会改变持久存储器中的任何内容,我是对的吗? - charlax

0

您可以替换(或删除)持久化存储,然后重置上下文(只需确保重新获取内存中的任何对象):

for (NSPersistentStore *store in persistentStoreCoordinator.persistentStores) {
    removed = [persistentStoreCoordinator removePersistentStore:store error:nil];
}

// You could delete the store here instead of replacing it if you want to start from scratch
[[NSFileManager defaultManager] replaceItemAtURL:storeURL
                                   withItemAtURL:newStoreURL
                                  backupItemName:nil
                                         options:0
                                resultingItemURL:nil
                                           error:nil];

NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];

[myContext reset];

我正在使用这个来将数据导入到一个私有队列上的临时存储中,然后在完成后覆盖主线程存储并重新加载所有内容。我曾尝试将数据导入到子上下文中,但在我的情况下,删除现有对象而不产生副作用变得很麻烦。


3
警告:SQLite 数据库通常由多个文件组成,仅移动其中一个文件可能会导致问题,如果您有任何未完成的事务(或其他实现定义的状态,但未完成的事务是最大的一个)。 - Stripes

0
在重置persistentStore之前,必须先重置与其关联的所有managedObjectContext,否则所有的managedObjects将无法访问上下文,可能会导致错误。
最好直接从文件系统中删除sqlite文件,并将managedObjectContext和persistentStoreCoordinator设置为nil,而不是调用removePersistentStore。这将在下次尝试访问或开始存储时重新创建persistentStore和managedObjectContext。

-4
我发现处理这些问题最简单的方法(只要您可以重新加载Core Data数据)是遵循以下步骤:
(1)重置模拟器。
(2)从您的项目中删除sqlite数据库。
(3)从计算机中删除模拟器目录。
令人惊讶的是,我发现仅执行步骤1和2有时不能生效,还需要执行步骤3。
如果这样做,您需要重新加载Core Data存储。

1
这并没有回答原问题,即如何在生产环境中运行时重置持久存储中的数据。 - Jacob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接