2010-08-10 32 views
117

Tôi đang gặp vấn đề trong nhiều giờ và đọc tất cả mọi thứ về điều này trên stackoverflow (và áp dụng mọi lời khuyên), bây giờ tôi chính thức cần giúp đỡ. ; O)"Bộ sưu tập đã bị đột biến khi được liệt kê" trên executeFetchRequest

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

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

  • Save the moc chính
  • Khởi tạo một moc nền với điều phối viên cửa hàng liên tục được sử dụng bởi moc chính
  • đăng ký điều khiển của tôi như là một người quan sát của thông báo NSManagedObjectContextDidSaveNotification cho moc nền
  • Gọi phương pháp nhập khẩu đối với một sợi nền
  • Mỗi dữ liệu thời gian được nhận, chèn nó vào moc nền
  • Khi tất cả các dữ liệu đã được nhập khẩu, lưu nền moc
  • Merge những thay đổi vào moc chính, trên các chủ đề chính
  • Unregister điều khiển của tôi như là một người quan sát cho các thông báo
  • Reset và nhả moc nền

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

*** 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 ALR dữ liệu nhập khẩu eady tồn tại trong cơ sở dữ liệu. Tôi tự hỏi điều gì đang làm đột biến 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 của tôi và đơn vị thử nghiệm của tôi (dự án của tôi gồm hai loại cổ phiếu này và các đại biểu ứng dụng, đã được chưa 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 hơn là một luồng nền nhập dữ liệu trên một nền moc.

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

Tôi khá chắc chắn rằng tôi thiếu điều gì đó hiển nhiên và điều đó khiến tôi phát điên.

EDIT:

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

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' 
+1

Trong menu Run của Xcode, 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ì? –

+1

Nó xác nhận rằng sự cố ứng dụng trên dòng "executeFetchRequest: error:". Tôi đã thêm dấu vết ngăn xếp đầy đủ vào câu hỏi ban đầu của mình ... –

+0

Và còn các chủ đề khác thì sao? –

Trả lời

175

OK, tôi nghĩ rằng tôi đã giải quyết vấn đề của tôi và tôi phải cảm ơn bài viết trên blog này từ Fred McCann:

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

Sự cố có vẻ xuất phát từ thực tế là tôi khởi tạo nền moc của mình trên chuỗi chính thay vì chuỗi nền. Khi Apple nói rằng mỗi thread cần phải có moc riêng của nó, bạn phải coi trọng nó: mỗi moc 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 pháp _importData (ngay trước khi đăng ký bộ điều khiển như quan sát viên cho các 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 ngay sau khi tôi được phép ... –

+0

Cảm ơn bạn đã giải pháp này! Chuỗi này có triển khai khóa/mở khóa rất tốt để tránh xung đột trong khi hợp nhất: http://stackoverflow.com/questions/2009399/cryptic-error-from-core-data-nsinvalidargumentexception-reason-referencedata64 – gonso

+4

+1 Cảm ơn bạn rất nhiều cho việc đặt câu hỏi, giải pháp và cung cấp liên kết tới bài đăng trên blog của Fred McCann .. Nó đã giúp tôi rất nhiều !!! – learner2010

0

Tôi đang làm việc để nhập bản ghi & hiển thị bản ghi trong chế độ xem bảng. Đối mặt với cùng một vấn đề 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ế trên mã với dưới một

[self saveObjectContextInDataBaseWithContext:privateQueueContext]; 

Thực sự nó là công việc ngu ngốc của tôi để tiết kiệm sợi nền trong khi tôi đã tạo ra một privateQueueConcurrencyType để lưu hồ sơ.

Các vấn đề liên quan