来自Core Data的隐秘错误:NSInvalidArgumentException,原因:referenceData64仅为抽象类定义

时间:2010-01-05 21:56:58

标签: iphone exception core-data

我正在做一个从XML文件中读取数据的iPhone应用程序,将它们转换为Core Data Managed Objects并保存它们。

应用程序工作正常,主要是在包含~150个对象的较小数据集/ XML上。我说的主要是因为10%的时间,我在尝试保存上下文时会从CoreData获得以下异常:

*由于未捕获的异常'NSInvalidArgumentException'而终止应用程序,原因:'* -_referenceData64仅为抽象类定义。定义 - [NSTemporaryObjectID_default _referenceData64]!'

在更大的数据集(~2000)上,每次都会发生这种情况,但不是在同一个地方。它可能在第137个记录,第580个或最后一个记录中失败。我试过移动保存点(每个对象,每10个对象,一旦所有对象都被alloc / init'保存),但我总是遇到上面的异常。

我搜索了这个例外,看到有人遇到了同样的问题,但没有看到任何解决方案。

我的下一步是将托管对象和关系简化为此错误停止并从那里构建以隔离问题的点。最后的办法是放弃Core Data,直接存入sqllite。

感谢您的帮助!

7 个答案:

答案 0 :(得分:30)

我有同样的问题。它适用于较小的数据集,但对于较大的集合,我得到“_referenceData64仅为抽象类定义”错误。我的模型中没有抽象实体。

编辑:

我想我已经解决了这个问题。在我的案例中的问题是我的线程混乱。以下是我修复它的指导原则:

  1. 我在一个线程中解析XML数据。在启动所述线程时,使用与主线程的NSManagedObjectContext相同的持久存储协调器创建一个新的NSManagedObjectContext。
  2. 您应该为线程的NSManagedObjectContext创建在线程中创建的任何新对象。如果必须从主线程的NSManagedObjectContext复制对象,请按ID复制。即
    NSManagedObjectID *objectID = [foo objectID];
    FooClass *newFoo = [(FooClass*)[threadManagedObjectContext objectWithID:objectID] retain]
  3. 完成解析后,您需要保存对线程的NSManagedObjectContext所做的更改。您必须锁定持久性存储协调器。我使用了以下(不完整的代码):
  4. `

     - (void)onFinishParsing {  
      // lock the store we share with main thread's context  
      [persistentStoreCoordinator lock];  
    
      // save any changes, observe it so we can trigger merge with the actual context  
      @try {  
        [threadManagedObjectContext processPendingChanges];  
      }  
      @catch (NSException * e) {  
        DLog(@"%@", [e description]);  
        [persistentStoreCoordinator unlock];  
      }  
      @finally {  
        // pass  
      }  
    
      NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];  
      [dnc addObserver:self selector:@selector(threadControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:threadManagedObjectContext];  
      @try {  
        NSError *error;  
        if (![threadManagedObjectContext save:&error]) {  
          DLog(@"%@", [error localizedDescription]);  
          [persistentStoreCoordinator unlock];  
          [self performSelectorOnMainThread:@selector(handleSaveError:) withObject:nil waitUntilDone:NO];  
        }  
      } @catch (NSException *e) {  
        DLog(@"%@", [e description]);  
        [persistentStoreCoordinator unlock];  
      } @finally {  
        // pass  
      }  
      [dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:threadManagedObjectContext];  
    
      [self performSelectorOnMainThread:@selector(parserFinished:) withObject:nil waitUntilDone:NO];  
    }  
    
    // Merging changes causes the fetched results controller to update its results  
    - (void)threadControllerContextDidSave:(NSNotification*)saveNotification {  
      // need to unlock before we let main thread merge  
      [persistentStoreCoordinator unlock];  
      [self performSelectorOnMainThread:@selector(mergeToMainContext:) withObject:saveNotification waitUntilDone:YES];  
    }  
    
    - (void)mergeToMainContext:(NSNotification*)saveNotification {  
      NSError *error;  
      [managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];  
      if (![managedObjectContext save:&error]) {  
        DLog(@"%@", [error localizedDescription]);  
        [self handleSaveError:nil];  
      }  
    }  
    

    `

答案 1 :(得分:12)

您必须遵守以下规则:

  

NSManagedObjectContext必须在使用的同一个线程上创建   它。 (或换句话说,每个线程必须有自己的MOC)

违反上述规则会导致以下异常:

  
      
  • *** -_referenceData64中的异常仅为抽象类定义。定义 - [NSTemporaryObjectID_default _referenceData64]!,
  •   

有人可能面临的另一个问题是,如果使用NSFetchedResultsController,则不会在UI类上调用委托。

我希望这个答案可以帮助别人!

答案 2 :(得分:2)

谢谢大家,我能够按照你的提示摆脱讨厌的异常。

线程问题似乎导致异常。在我的例子中,我有主线程产生工作线程,它获取XML,解析,创建必要的托管对象,并保存它们。

我通过使用performSelectorOnMainThread进行保存尝试了一种懒惰的方式,但是没有用。

我的最后一种方法是使用它自己的ManagedObjectContext创建一个名为ThreadDataService的类,每个线程都有一个ThreadDataService实例,基本上就是Adriaan所建议的。

再次感谢所有答案。你们好棒!

答案 3 :(得分:2)

nsmanagedobject数据的映射和managedobjectcontext保存在下一个块中,以便锁定managedobjectcontext以防止另一个线程访问并解决崩溃问题。

[context performBlockAndWait:^{

    //your code
    [context save:&error];

}];

答案 4 :(得分:1)

抱歉我的英语(我是法国人)。 我确实遇到了同样的问题,我意识到我从第二个线程调用了Core Data Framework上的一个方法(插入一个对象)。我只是使用performSelectorOnMainThread从主线程调用此方法,它解决了我的问题。 我希望它会对你有所帮助。

答案 5 :(得分:1)

我有同样的问题,在寻找答案时我发现了这个问题。我的问题是我启动了两个在同一个托管上下文中工作的线程在保存到持久性存储时崩溃 - 如果每个线程都有自己的上下文,则不会出现问题。但它可能只是通过锁定持久存储来解决,但我相信2个托管上下文是正确的解决方案。

此致

答案 6 :(得分:0)

简短答案: 您必须在保存上下文时使用以下函数,以确保在为上下文指定的队列上执行块操作。

perform(_:)
performAndWait(_:) 
Apple的NSManagedObject并发部分中提到的

here

详细答案: 如Apple文档中所述

Core Data使用线程(或序列化队列)限制来保护 托管对象和托管对象上下文(请参阅核心数据编程) 指南)。这样的结果是上下文假定默认 owner是为其分配线程或队列的对象,具体取决于 调用其init方法的线程。因此,您不应 在一个线程上初始化上下文,然后将其传递给另一个线程。 相反,您应该将引用传递给持久性存储协调器 并让接收线程/队列创建一个从其派生的新上下文 那。

由此我们可以假设

  • 创建NSManagedObject的线程将拥有该对象
  • 您无法在一个线程中创建NSManagedObject并将其保存在 其他对象(由于错误)
  • 为每个线程创建单独的NSManagedObject或通过 id由Adriaan回答。

这会在您脑海中引发一个新问题

有没有办法找出NSManagedObjectContext是哪个线程 上吗?

已被回答here 和汤姆·哈灵顿(Tom Harrington)的回答将证明每件事都有些不准确(我也相信),并将我们带回到简短的答案:)