1

このエラーに関するSOのすべてを読みましたが、アプリでなぜそれが起こっているのかを突き止めることはできません.

バックグラウンド コンテキストを使用して複数の 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'sが呼び出されます。addArticle追加する記事が 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

わかりましたので、公式ドキュメントを読むことは多少役に立ちます。

アップルから(強調鉱山):

同時実行

コア データは、スレッド (またはシリアル化されたキュー) 制限を使用して、管理対象オブジェクトと管理対象オブジェクト コンテキストを保護します (「コア データとの同時実行」を参照)。この結果、コンテキストは、デフォルトの所有者がそれを割り当てたスレッドまたはキューであると想定します。これは、その init メソッドを呼び出すスレッドによって決定されます。したがって、あるスレッドでコンテキストを初期化してから、それを別のスレッドに渡すべきではありません。代わりに、永続ストア コーディネーターへの参照を渡し、受信スレッド/キューにそこから派生した新しいコンテキストを作成させる必要があります。NSOperation を使用する場合は、main (シリアル キューの場合) または start (同時キューの場合) にコンテキストを作成する必要があります。

したがって、私の問題は、メイン スレッドでバックグラウンド コンテキストを初期化した後、dispatch_async(メイン スレッドで作成されたコンテキストを使用して) バックグラウンド スレッドで保存を実行する Grand Central Dispatch を使用したことでした。

背景ブロックにコンテキストの初期化を追加して修正しました。

-(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 が作成されたスレッドでのみ MOC を使用することを保証する必要があります。

NSPrivateQueueConcurrencyType を使用して MOC を作成し、使用するだけです。

[moc performBlock:^{
}];

操作を実行します。独自の内部キューがあり、すべてのリクエストをバックグラウンドで実行し、アクセスを他の呼び出しと同期します。

NSMainQueueConcurrencyType を使用して、MOC をメイン スレッドでのみ実行するように関連付けることができます。

于 2012-08-08T21:08:11.393 に答える