"Bộ sưu tập đã bị thay đổi khi được liệt kê" trên executeFetchRequest


121

Tôi đang mắc kẹt với một vấn đề trong nhiều giờ và đã đọc mọi thứ về vấn đề này trên stackoverflow (và áp dụng mọi lời khuyên được tìm thấy), tôi chính thức cần được trợ giúp. ; o)

Đây là bối cảnh:

Trong dự án iPhone của tôi, tôi cần nhập dữ liệu trên nền và chèn nó vào ngữ cảnh đối tượng được quản lý. Làm theo lời khuyên được tìm thấy ở đây, đây là những gì tôi đang làm:

  • Lưu moc chính
  • Tạo moc nền với trình điều phối cửa hàng liên tục được moc chính sử dụng
  • Đăng ký bộ điều khiển của tôi như một người quan sát thông báo NSManagedObjectContextDidSaveNotification cho moc nền
  • Gọi phương thức nhập trên một chuỗi nền
  • Mỗi khi dữ liệu được nhận, hãy chèn nó vào moc nền
  • Sau khi tất cả dữ liệu đã được nhập, hãy lưu moc nền
  • Hợp nhất các thay đổi vào moc chính, trên chuỗi chính
  • Hủy đăng ký người kiểm soát của tôi với tư cách là người quan sát thông báo
  • Đặt lại và phát hành moc nền

Đôi khi (và ngẫu nhiên), ngoại lệ ...

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...

... được ném khi tôi gọi executeFetchRequest trên moc nền, để kiểm tra xem dữ liệu đã nhập đã tồn tại trong cơ sở dữ liệu chưa. Tôi tự hỏi điều gì đang thay đổi tập hợp vì không có gì chạy bên ngoài phương thức nhập.

Tôi đã bao gồm toàn bộ mã của bộ điều khiển và thực thể thử nghiệm của mình (dự án của tôi bao gồm hai lớp này và đại biểu ứng dụng, chưa được sửa đổi):

//
//  RootViewController.h
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import <CoreData/CoreData.h>

@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectContext *backgroundMOC;
}


@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSManagedObjectContext *backgroundMOC;

@end


//
//  RootViewController.m
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import "RootViewController.h"
#import "FK1Message.h"

@implementation RootViewController

@synthesize managedObjectContext;
@synthesize backgroundMOC;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationController.toolbarHidden = NO;

    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshAction:)];

    self.toolbarItems = [NSArray arrayWithObject:refreshButton];
}

#pragma mark -
#pragma mark ACTIONS

- (void)refreshAction:(id)sender {
    // If there already is an import running, we do nothing

    if (self.backgroundMOC != nil) {
        return;
    }

    // We save the main moc

    NSError *error = nil;

    if (![self.managedObjectContext save:&error]) {
        NSLog(@"error = %@", error);

        abort();
    }

    // We instantiate the background moc

    self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];

    [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    // We call the fetch method in the background thread

    [self performSelectorInBackground:@selector(_importData) withObject:nil];
}

- (void)_importData {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundMOCDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];         

    FK1Message *message = nil;

    NSFetchRequest *fetchRequest = nil;
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
    NSPredicate *predicate = nil;
    NSArray *results = nil;

    // fake import to keep this sample simple

    for (NSInteger index = 0; index < 20; index++) {
        predicate = [NSPredicate predicateWithFormat:@"msgId == %@", [NSString stringWithFormat:@"%d", index]];

        fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

        [fetchRequest setEntity:entity];
        [fetchRequest setPredicate:predicate];

        // The following line sometimes randomly throw the exception :
        // *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5b71a00> was mutated while being enumerated.

        results = [self.backgroundMOC executeFetchRequest:fetchRequest error:NULL];

        // If the message already exist, we retrieve it from the database
        // If it doesn't, we insert a new message in the database

        if ([results count] > 0) {
            message = [results objectAtIndex:0];
        }
        else {
            message = [NSEntityDescription insertNewObjectForEntityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
            message.msgId = [NSString stringWithFormat:@"%d", index];
        }

        // We update the message

        message.updateDate = [NSDate date];
    }

    // We save the background moc which trigger the backgroundMOCDidSave: method

    [self.backgroundMOC save:NULL];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];

    [self.backgroundMOC reset]; self.backgroundMOC = nil;

    [pool drain];
}

- (void)backgroundMOCDidSave:(NSNotification*)notification {    
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundMOCDidSave:) withObject:notification waitUntilDone:YES];
        return;
    }

    // We merge the background moc changes in the main moc

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

@end

//
//  FK1Message.h
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <CoreData/CoreData.h>

@interface FK1Message :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString * msgId;
@property (nonatomic, retain) NSDate * updateDate;

@end

// 
//  FK1Message.m
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "FK1Message.h"

@implementation FK1Message 

#pragma mark -
#pragma mark PROPERTIES

@dynamic msgId;
@dynamic updateDate;

@end

Đây là tất cả ! Toàn bộ dự án là ở đây. Không có chế độ xem bảng, không có NSFetchedResultsController, không có gì khác ngoài luồng nền nhập dữ liệu trên moc nền.

Điều gì có thể thay đổi tập hợp trong trường hợp này?

Tôi khá chắc rằng mình đang bỏ lỡ một điều gì đó hiển nhiên và điều đó khiến tôi phát điên.

BIÊN TẬP:

Đây là dấu vết ngăn xếp đầy đủ:

    2010-08-10 10:29:11.258 FK1[51419:1b6b] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5d075b0> was mutated while being enumerated.<CFBasicHash 0x5d075b0 [0x25c6380]>{type = mutable set, count = 0,
entries =>
}
'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x0255d919 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x026ab5de objc_exception_throw + 47
    2   CoreFoundation                      0x0255d3d9 __NSFastEnumerationMutationHandler + 377
    3   CoreData                            0x02287702 -[NSManagedObjectContext executeFetchRequest:error:] + 4706
    4   FK1                                 0x00002b1b -[RootViewController _fetchData] + 593
    5   Foundation                          0x01d662a8 -[NSThread main] + 81
    6   Foundation                          0x01d66234 __NSThread__main__ + 1387
    7   libSystem.B.dylib                   0x9587681d _pthread_start + 345
    8   libSystem.B.dylib                   0x958766a2 thread_start + 34
)
terminate called after throwing an instance of 'NSException'

2
Trong menu Run của Xcode, hãy bật “Stop on Objective-C Exceptions”, sau đó chạy ứng dụng của bạn trong Debugger. Bạn tìm thấy gì?
Peter Hosey

1
Nó xác nhận rằng sự cố ứng dụng trên dòng "executeFetchRequest: error:". Tôi đã thêm stack trace đầy đủ cho câu hỏi ban đầu của tôi ...
Eric MORAND

Và những gì về các chủ đề khác?
Peter Hosey

Hmmm, đây là chủ đề chính stack: # 0 0x958490fa trong mach_msg_trap # 1 0x95849867 trong mach_msg # 2 0x0253f206 trong __CFRunLoopServiceMachPort # 3 0x0249c8b4 trong __CFRunLoopRun # 4 0x0249c280 trong CFRunLoopRunSpecific # 5 0x0249c1a1 trong CFRunLoopRunInMode # 6 0x027a82c8 trong GSEventRunModal # 7 0x027a838d trong GSEventRun # 8 0x00021b58 trong UIApplicationMain # 9 0x00001edc trong main tại main.m: 16 Có 2 luồng khác (libdispatch-manager và "WebThread") nhưng chúng không cung cấp thêm thông tin.
Eric MORAND

Câu trả lời:


182

OK, tôi nghĩ rằng tôi đã giải quyết được vấn đề của mình và tôi phải cảm ơn bài đăng blog này từ Fred McCann's:

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

Vấn đề dường như đến từ thực tế là tôi khởi tạo moc nền của mình trên luồng chính thay vì luồng nền. Khi Apple nói rằng mỗi luồng cần phải có mã riêng của nó, bạn phải nghiêm túc xem xét nó: mỗi mã phải được khởi tạo trong luồng sẽ sử dụng nó!

Di chuyển các dòng sau ...

// We instantiate the background moc

self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];

[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

... trong phương thức _importData (ngay trước đó để đăng ký bộ điều khiển làm người quan sát thông báo) giải quyết vấn đề.

Cảm ơn sự giúp đỡ của bạn, Peter. Và cảm ơn Fred McCann về bài đăng trên blog có giá trị của nó!


2
OK, sau rất nhiều thử nghiệm, tôi có thể xác nhận rằng điều này hoàn toàn giải quyết được vấn đề của tôi. Tôi sẽ đánh dấu đây là câu trả lời được chấp nhận càng sớm càng tôi được phép ...
Eric MORAND

Cảm ơn cho giải pháp này! Chủ đề này có cách triển khai rất tốt ngữ cảnh khóa / mở khóa để tránh xung đột trong quá trình hợp nhất: stackoverflow.com/questions/2009399/…
gonso

4
+1 Cảm ơn rất nhiều vì đã đưa ra câu hỏi, giải pháp và cung cấp liên kết đến bài đăng trên blog của Fred McCann .. Nó đã giúp tôi rất nhiều !!!
learningner2010

3
each moc must be instantiated in the thread that will be using itTôi mặc dù chỉ hoạt động trên MOC nên trên cùng một chuỗi, nhưng việc tạo MOC cũng vậy, nếu đây là MOC riêng tư, hàng đợi liên quan vẫn chưa tồn tại ..
János

@ János Tôi có cùng một câu hỏi ở đây. Làm thế nào bạn có thể khởi tạo ngữ cảnh trong chuỗi sẽ sử dụng nó? Chủ đề vẫn chưa tồn tại. Tôi đang sử dụng Swift và tôi không hiểu "di chuyển trong phương thức _importData" nghĩa là gì.
Todanley

0

Tôi đang làm việc về việc nhập bản ghi và hiển thị bản ghi trong chế độ xem bảng. Gặp phải vấn đề tương tự khi tôi cố gắng lưu bản ghi trên backgroundThread như bên dưới

 [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];

trong khi tôi đã tạo một PrivateQueueContext. Chỉ cần thay thế mã bên trên bằng mã bên dưới

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

Thực sự đó là việc làm ngu ngốc của tôi khi lưu trên chuỗi nền trong khi tôi đã tạo privateQueueConcurrencyType để lưu bản ghi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.