“ คอลเลกชันถูกกลายพันธุ์ในขณะที่ถูกแจกแจง” บน executeFetchRequest


121

ตอนนี้ฉันติดปัญหามาหลายชั่วโมงแล้วและอ่านทุกอย่างเกี่ยวกับสิ่งนี้ใน stackoverflow (และใช้ทุกคำแนะนำที่พบ) ตอนนี้ฉันต้องการความช่วยเหลืออย่างเป็นทางการ ; o)

นี่คือบริบท:

ในโครงการ iPhone ของฉันฉันต้องนำเข้าข้อมูลบนพื้นหลังและแทรกในบริบทวัตถุที่มีการจัดการ ตามคำแนะนำที่พบที่นี่นี่คือสิ่งที่ฉันกำลังทำ:

  • บันทึก moc หลัก
  • จัดเตรียมม็อคพื้นหลังโดยใช้ผู้ประสานงานร้านค้าถาวรที่ใช้โดยมอคหลัก
  • ลงทะเบียนคอนโทรลเลอร์ของฉันเป็นผู้สังเกตการณ์ของการแจ้งเตือน NSManagedObjectContextDidSaveNotification สำหรับ moc พื้นหลัง
  • เรียกใช้วิธีการนำเข้าบนเธรดพื้นหลัง
  • ทุกครั้งที่ได้รับข้อมูลให้ใส่ข้อมูลลงในพื้นหลัง moc
  • เมื่อนำเข้าข้อมูลทั้งหมดแล้วให้บันทึกพื้นหลัง moc
  • รวมการเปลี่ยนแปลงเข้ากับ moc หลักบนเธรดหลัก
  • ยกเลิกการลงทะเบียนคอนโทรลเลอร์ของฉันเป็นผู้สังเกตการณ์สำหรับการแจ้งเตือน
  • รีเซ็ตและปล่อยพื้นหลัง moc

บางครั้ง (และสุ่ม) ข้อยกเว้น ...

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

... ถูกโยนทิ้งเมื่อฉันเรียก executeFetchRequest บนพื้นหลัง moc เพื่อตรวจสอบว่าข้อมูลที่นำเข้ามีอยู่แล้วในฐานข้อมูลหรือไม่ ฉันสงสัยว่าอะไรกำลังกลายพันธุ์ชุดเนื่องจากไม่มีสิ่งใดที่ทำงานนอกวิธีการนำเข้า

ฉันได้รวมรหัสทั้งหมดของคอนโทรลเลอร์และเอนทิตีการทดสอบของฉัน (โครงการของฉันประกอบด้วยสองคลาสนี้และตัวแทนแอปซึ่งไม่ได้รับการแก้ไข):

//
//  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

นี่คือทั้งหมดที่ ! ทั้งโครงการอยู่ที่นี่ ไม่มีมุมมองตารางไม่มี NSFetchedResultsController ไม่มีอะไรอื่นนอกจากเธรดพื้นหลังที่นำเข้าข้อมูลบนพื้นหลัง moc

อะไรที่สามารถทำให้ชุดกลายพันธุ์ได้ในกรณีนี้?

ฉันค่อนข้างแน่ใจว่าฉันพลาดอะไรบางอย่างที่ชัดเจนและมันทำให้ฉันโมโห

แก้ไข:

นี่คือการติดตามสแต็กแบบเต็ม:

    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
ในเมนู Run ของ Xcode ให้เปิด“ Stop on Objective-C exceptions” จากนั้นเรียกใช้แอปของคุณภายใต้ตัวดีบักเกอร์ คุณเจออะไร?
Peter Hosey

1
เป็นการยืนยันว่าแอปขัดข้องในบรรทัด "executeFetchRequest: error:" ฉันได้เพิ่มการติดตามสแต็กแบบเต็มในคำถามเดิมของฉันแล้ว ...
Eric MORAND

แล้วกระทู้อื่น ๆ ล่ะ?
Peter Hosey

อืมมนี่คือกองหัวข้อหลัก: # 0 0x958490fa ใน mach_msg_trap # 1 0x95849867 ใน mach_msg # 2 0x0253f206 ใน __CFRunLoopServiceMachPort # 3 0x0249c8b4 ใน __CFRunLoopRun # 4 0x0249c280 ใน CFRunLoopRunSpecific # 5 0x0249c1a1 ใน CFRunLoopRunInMode # 6 0x027a82c8 ใน GSEventRunModal # 7 0x027a838d ใน GSEventRun # 8 0x00021b58 ใน UIApplicationMain # 9 0x00001edc ใน main ที่ main.m: 16 มีเธรดอื่นอีก 2 เธรด (libdispatch-manager และ "WebThread") แต่ไม่ได้ให้ข้อมูลเพิ่มเติม
เอริคมอร์แลนด์

คำตอบ:


182

ตกลงฉันคิดว่าฉันแก้ปัญหาได้แล้วและฉันต้องขอบคุณบล็อกโพสต์นี้จาก Fred McCann's:

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

ปัญหาดูเหมือนจะมาจากการที่ฉันสร้างอินสแตนซ์พื้นหลัง moc บนเธรดหลักแทนที่จะเป็นเธรดพื้นหลัง เมื่อ Apple บอกว่าแต่ละเธรดจำเป็นต้องมีม็อคของตัวเองคุณต้องให้ความสำคัญอย่างจริงจัง: ม็อคแต่ละอันจะต้องอินสแตนซ์ในเธรดที่จะใช้มัน!

การย้ายบรรทัดต่อไปนี้ ...

// We instantiate the background moc

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

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

... ในเมธอด _importData (ก่อนหน้านี้เพื่อลงทะเบียนคอนโทรลเลอร์เป็นผู้สังเกตการณ์สำหรับการแจ้งเตือน) แก้ปัญหาได้

ขอบคุณสำหรับความช่วยเหลือของคุณปีเตอร์ และขอบคุณ Fred McCann สำหรับบล็อกโพสต์ที่มีค่า!


2
ตกลงหลังจากการทดสอบหลายครั้งฉันสามารถยืนยันได้ว่าสิ่งนี้ช่วยแก้ปัญหาของฉันได้อย่างแน่นอน ฉันจะทำเครื่องหมายว่าเป็นคำตอบที่ยอมรับทันทีที่ฉันได้รับอนุญาตให้ ...
Eric MORAND

ขอบคุณสำหรับการแก้ปัญหานี้! เธรดนี้มีการใช้บริบทการล็อก / ปลดล็อกที่ดีมากเพื่อหลีกเลี่ยงความขัดแย้งระหว่างการผสาน: stackoverflow.com/questions/2009399/…
gonso

4
+1 ขอบคุณมากที่ตอบคำถามวิธีแก้ปัญหาและให้ลิงค์ไปยังบล็อกโพสต์ของ Fred McCann .. มันช่วยฉันได้มาก !!!
learner2010

3
each moc must be instantiated in the thread that will be using itฉันแม้ว่าการดำเนินการเฉพาะบน MOC ควรอยู่ในเธรดเดียวกัน แต่การสร้าง MOC เองด้วยเช่นกันหากนี่เป็น MOC ส่วนตัวคิวที่เกี่ยวข้องยังไม่มี ..
János

@ Jánosฉันมีคำถามเดียวกันที่นี่ คุณสามารถสร้างอินสแตนซ์บริบทในเธรดที่จะใช้มันได้อย่างไร กระทู้ยังไม่มี ฉันใช้ Swift และฉันไม่เข้าใจว่า "การย้ายในเมธอด _importData" หมายถึงอะไร
Todanley

0

ฉันกำลังดำเนินการนำเข้าบันทึกและการแสดงระเบียนใน tableview ประสบปัญหาเดียวกันเมื่อฉันพยายามบันทึกใน backgroundThread เหมือนด้านล่าง

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

ในขณะที่ฉันสร้าง PrivateQueueContext แล้ว เพียงแทนที่โค้ดด้านบนด้วยโค้ดด้านล่าง

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

จริงๆแล้วมันเป็นงานที่โง่เขลาของฉันที่จะบันทึกเธรดพื้นหลังในขณะที่ฉันสร้าง privateQueueConcurrencyType สำหรับบันทึกบันทึกแล้ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.