1

我已经阅读了关于这个错误的所有内容,但仍然无法确定它为什么会在我的应用程序中发生。

使用后台上下文保存多个 Core Data 对象时出现以下错误:

*** Terminating app due to uncaught exception "NSInternalInconsistencyException", reason: "Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.

在下面的代码中,ArticleManager'saddArticle在主线程的循环中被调用。可能有 0-200 多篇文章要添加。此错误通常发生在文章计数 100-150 之间。

//ArticleManager.m

-(id)init
{
    ... //normal init stuff
    dispatch_queue_t request_queue = dispatch_queue_create("com.app.articleRequest", NULL);
}    

-(void) addArticle:(Article *)article withURLKey:(NSString *)url
{
    //check if exists
    if ([downloadedArticles objectForKey:url] == nil && article != nil)
    {
        //add locally
        [downloadedArticles setObject:article forKey:url];

        //save to core data
        SaveArticle *saveArticle = [[SaveArticle alloc] init];
        [saveArticle saveArticle:article withURL:url onQueue:request_queue];
    }
}
//SaveArticle.m

@implementation SaveArticle

@synthesize managedObjectContext;
@synthesize backgroundContext;

-(id)init
{
    if (![super init]) return nil;

    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    managedObjectContext = [appDelegate managedObjectContext];

    backgroundContext = [[NSManagedObjectContext alloc] init];
    [backgroundContext setPersistentStoreCoordinator:[managedObjectContext persistentStoreCoordinator]];

    return self;
}

-(void)saveArticle:(Article *)article withURL:(NSString *)url onQueue:(dispatch_queue_t)queue
{       
    //save persistently in the background
    dispatch_async(queue, ^{
        ArticleCache *articleCacheObjectModel = (ArticleCache *)[NSEntityDescription insertNewObjectForEntityForName:@"ArticleCache" inManagedObjectContext:backgroundContext];

        if (article != nil)
        {
            [articleCacheObjectModel setArticleHTML:article.articleHTML];
            [articleCacheObjectModel setUrl:url];

            NSError *error;

            //Save the background context and handle the save notification 
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(backgroundContextDidSave:)
                                                         name:NSManagedObjectContextDidSaveNotification
                                                       object:backgroundContext];

            if(![backgroundContext save:&error]) //ERROR OCCURS HERE, after many saves
            {  
                //This is a serious error saying the record  
                //could not be saved. Advise the user to  
                //try again or restart the application.
            }

            [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:backgroundContext];

        }
    });
}

/* Save notification handler for the background context */
- (void)backgroundContextDidSave:(NSNotification *)notification {
    /* Make sure we're on the main thread when updating the main context */
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                               withObject:notification
                            waitUntilDone:NO];
        return;
    }

    /* merge in the changes to the main context */
    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

@end
4

2 回答 2

1

好的,所以阅读官方文档有点用处。

来自苹果(重点是我的):

并发

Core Data 使用线程(或序列化队列)限制来保护托管对象和托管对象上下文(请参阅“Core Data 的并发性”)。这样做的结果是上下文假定默认所有者是分配它的线程或队列——这由调用其 init 方法的线程确定。因此,您不应该在一个线程上初始化上下文,然后将其传递给另一个线程。相反,您应该将引用传递给持久存储协调器,并让接收线程/队列创建一个从中派生的新上下文。如果使用 NSOperation,则必须在 main(对于串行队列)或 start(对于并发队列)中创建上下文。

所以我的问题是我在主线程上初始化了后台上下文,但随后使用 Grand Central Dispatch 通过dispatch_async它在后台线程上执行保存(使用在主线程上创建的上下文)。

我通过将上下文初始化添加到背景块来修复它:

-(void)saveArticle:(Article *)article withURL:(NSString *)url onQueue:(dispatch_queue_t)queue
{       
    //save persistently in the background
    dispatch_async(queue, ^{

        NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
        [backgroundContext setPersistentStoreCoordinator:[managedObjectContext persistentStoreCoordinator]];

        ArticleCache *articleCacheObjectModel = (ArticleCache *)[NSEntityDescription insertNewObjectForEntityForName:@"ArticleCache" inManagedObjectContext:backgroundContext];

        if (article != nil)
        {
            [articleCacheObjectModel setArticleHTML:article.articleHTML];
            [articleCacheObjectModel setUrl:url];

            NSError *error;

            //Save the background context and handle the save notification 
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(backgroundContextDidSave:)
                                                         name:NSManagedObjectContextDidSaveNotification
                                                       object:backgroundContext];

            if(![backgroundContext save:&error])
            {  
                //This is a serious error saying the record  
                //could not be saved. Advise the user to  
                //try again or restart the application.
            }

            [[NSNotificationCenter defaultCenter] removeObserver:self
                                                            name:NSManagedObjectContextDidSaveNotification
                                                          object:backgroundContext];
        }
    });
}
于 2012-08-08T19:38:58.163 回答
0

是的,如果您使用限制并发模型(这是您通过 init 获得的),那么您必须保证只在创建它的线程中使用 MOC。

您可以使用 NSPrivateQueueConcurrencyType 创建一个 MOC,然后使用

[moc performBlock:^{
}];

执行操作。它有自己的内部队列,并将在后台运行所有请求,与其他调用同步访问。

您可以使用 NSMainQueueConcurrencyType 将 MOC 绑定为仅在主线程上运行。

于 2012-08-08T21:08:11.393 回答