【问题标题】:Reset a CoreData persistent store重置 CoreData 持久存储
【发布时间】:2011-01-17 19:40:14
【问题描述】:

基本上,我要做的是清除我的 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_ACESS...

我不太了解CoreData或多线程,也许我正在做一个明显的错误......

【问题讨论】:

  • 我也很想知道怎么做。
  • 使用预写锁定 (WAL) 是个坏主意。磁盘上也会有 wal 和 shm 文件。

标签: iphone multithreading core-data


【解决方案1】:

对于那些在 iOS9+ 上尝试此操作的用户,现在有 destroyPersistentStoreAtURLreplacePersistentStoreAtURL API。

【讨论】:

  • 你知道解释如何使用这些方法的任何信息吗? Apple 的文档很少。
  • 特别不确定哪些选项适用,因为文档根本没有说明
【解决方案2】:

在重置persistentStore之前,您必须先重置与其关联的所有managedObjectContext,否则所有managedObjects将没有上下文可以访问,这可能会导致错误。

最好总是直接从文件系统中删除 sqlite 文件并将 ma​​nagedObjectContextpersistentStoreCoordinator 设置为 nil,而不是调用 removePersistentStore。 这将在您下次尝试访问或开始存储时重新创建 persistantStore 和 managedObjectContext。

【讨论】:

    【解决方案3】:

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

    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];
    

    我正在使用它来执行导入到私有队列上的临时存储中,然后覆盖主线程存储并在完成后重新加载所有内容。我正在导入子上下文,但在我的情况下删除没有副作用的现有对象变得很麻烦。

    【讨论】:

    • 警告:sqlite 数据库通常不止一个文件,如果您有任何待处理的事务(或其他实现定义的状态,但待处理的事务是最大的),只移动一个会导致问题。
    【解决方案4】:

    我发现处理这些类型的问题的最简单方法是,只要您可以重新加载核心数据数据,请按照以下步骤操作:

    (1) 重置模拟器。

    (2) 从你的项目中删除 sqlite 数据库。

    (3) 从你的机器上删除模拟器目录。

    令人惊讶的是,我发现如果没有第 3 步,执行第 1 步和第 2 步并不总是有效。

    如果您这样做,您将需要重新加载您的 Core Data 存储。

    【讨论】:

    • 这并没有解决原始问题,即如何在生产环境的运行时重置持久存储中的数据。
    【解决方案5】:

    还是不行!由具有无效持久性存储的 ManagedObjectContext 链接导致的中止。最后,如果我删除 ManagedObjectContext 并让应用程序稍后重新创建它会起作用

    这是我的修改

    - (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,您可以强制应用程序自行重新创建它,从而消除此错误。谢谢!
    【解决方案6】:

    这里是解决方案。可能有一些更优雅的选项(锁定...),但这一个有效。

    /**
     * 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;
    }
    

    【讨论】:

    • 这非常丑陋和过于复杂。为什么你不只是重建整个核心数据堆栈并将新的上下文交给你的视图控制器?更换NSManagedObjectContext 下的 PSC 可能非常冒险。其次,您永远不应该从多个线程中点击NSManagedObjectContext,堆栈的设计是为了您为每个线程创建一个上下文是有原因的;上下文处理锁定正确。您最好重建整个堆栈或实际从存储中删除对象,然后重新加载它,而不是删除文件。
    • 谢谢你,马库斯,我不确定听到你的声音;从广义上讲,您将如何清除数据库中的所有数据? CoreData 堆栈意味着什么?假设持久存储中已经有一些数据,我想删除所有内容然后导入新数据。如果我理解 Apple 的文档,重置 MOC 不会改变持久存储中的任何内容,对吗?
    【解决方案7】:

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

    来自NSManagedObjectContext's documentation

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

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

    【讨论】:

    • 只是为了确保理解:我的数据已经保存在持久存储中。一个简单的[moc reset](然后保存)也会擦除 sqlite 文件中的所有内容吗?
    • 这解决了困扰我的一个问题!谢谢!问题:如何更新被外部进程编辑的tableview中的数据:Invoke [myManagedObjectContext reset];在从 fetchedResultsController 调用新数据之前。我有很多快乐!
    • charlax,他没有明确说重置后保存确实会重置。
    • @Giao 删除persistentstore是否足够公平,或者这是一种不好的做法?请给我建议。
    • 不管怎样,重置上下文根本不会改变商店的内容。它只是删除了已加载到上下文中的所有内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 2014-02-15
    • 2011-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多