ExecuteFetchRequest üzerinde "Koleksiyon numaralandırılırken değiştirildi"


121

Saatlerdir bir soruna takılıp kaldım ve bununla ilgili her şeyi stackoverflow'da okudum (ve bulunan tüm tavsiyeleri uyguladıktan sonra), şimdi resmen yardıma ihtiyacım var. ;Ö)

İşte bağlam:

İPhone projemde, verileri arka planda içe aktarmam ve yönetilen bir nesne bağlamına eklemem gerekiyor. Burada bulunan tavsiyelere uyarak, işte yaptığım şey:

  • Ana moc'u kaydedin
  • Ana moc tarafından kullanılan kalıcı mağaza koordinatörü ile bir arka plan moc örneği oluşturun
  • Denetleyicimi arka plan moc için NSManagedObjectContextDidSaveNotification bildiriminin gözlemcisi olarak kaydet
  • Bir arka plan iş parçacığında içe aktarma yöntemini çağırın
  • Her veri alındığında, arka plan moc'a ekleyin
  • Tüm veriler içe aktarıldıktan sonra arka plan moc'unu kaydedin
  • Değişiklikleri ana ileti dizisinde birleştirin
  • Denetleyicimin bildirim için gözlemci olarak kaydını iptal et
  • Arka plan moc'unu sıfırlayın ve serbest bırakın

Bazen (ve rastgele) istisna ...

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

... içe aktarılan verilerin veritabanında zaten mevcut olup olmadığını kontrol etmek için arka planda executeFetchRequest'i çağırdığımda atılıyor. İçe aktarma yönteminin dışında çalışan hiçbir şey olmadığı için kümeyi neyin değiştirdiğini merak ediyorum.

Denetleyicimin ve test varlığımın tüm kodunu dahil ettim (bu iki sınıftan ve değiştirilmemiş uygulama temsilcisinden oluşan projem):

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

Hepsi bu ! Bütün proje burada. Tablo görünümü yok, NSFetchedResultsController yok, arka planda veri aktaran bir arka plan iş parçacığından başka bir şey yok.

Bu durumda seti ne değiştirebilir?

Açıkça bir şeyi kaçırdığımdan oldukça eminim ve bu beni deli ediyor.

DÜZENLE:

İşte tam yığın izleme:

    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
Xcode'un Çalıştır menüsünde, "Objective-C İstisnalarında Durdur" u açın, ardından uygulamanızı Hata Ayıklayıcı altında çalıştırın. Ne buluyorsun
Peter Hosey

1
"ExecuteFetchRequest: error:" satırındaki uygulamanın kilitlendiğini doğrular. Orijinal soruma tam yığın izlemeyi ekledim ...
Eric MORAND

Peki ya diğer konular?
Peter Hosey

Hmmm, burada ana iplik yığın: # 0 0x958490fa içinde mach_msg_trap mach_msg # içerisinde 1 0x95849867 __CFRunLoopServiceMachPort 2 0x0253f206 # __CFRunLoopRun 3 0x0249c8b4 # CFRunLoopRunSpecific 4 0x0249c280 # CFRunLoopRunInMode 5 0x0249c1a1 6. 0x027a82c8 GSEventRunModal # 7 0x027a838d GSEventRun # da 8 UIApplicationMain # 9 0x00001edc içinde main.m: 16'da 0x00021b58 Diğer 2 iş parçacığı daha var (libdispatch-manager ve "WebThread") ancak daha fazla bilgi vermiyorlar.
Eric MORAND

Yanıtlar:


182

Tamam, sanırım sorunumu çözdüm ve Fred McCann'in şu blog gönderisine teşekkür etmeliyim:

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

Sorun, arka plan moc'umu arka plan iş parçacığı yerine ana iş parçacığı üzerinde başlatmamdan kaynaklanıyor gibi görünüyor. Apple, her bir iş parçacığının kendi moc'una sahip olması gerektiğini söylediğinde, onu ciddiye almalısınız: her moc, onu kullanacak olan iş parçacığında somutlaştırılmalıdır!

Aşağıdaki satırların taşınması ...

// We instantiate the background moc

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

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

... _importData yönteminde (denetleyiciyi bildirim için gözlemci olarak kaydetmeden hemen önce) sorunu çözer.

Yardımın için teşekkürler, Peter. Ve değerli blog yazısı için Fred McCann'e teşekkürler!


2
Tamam, birçok testten sonra, bunun sorunumu kesinlikle çözdüğünü teyit edebilirim. İzin verdiğim anda bunu kabul edilmiş cevap olarak işaretleyeceğim ...
Eric MORAND

Bu çözüm için teşekkürler! Bu iş parçacığı, birleştirme sırasında çakışmaları önlemek için çok iyi bir kilitleme / kilit açma bağlamı uygulamasına sahiptir: stackoverflow.com/questions/2009399/…
gonso

4
+1 Soruyu, çözümü ortaya koyduğunuz ve Fred McCann'in blog gönderisine bağlantı sağladığınız için çok teşekkürler .. Bana çok yardımcı oldu !!!
öğrenci2010

3
each moc must be instantiated in the thread that will be using itSadece MOC üzerindeki işlemin aynı iş parçacığında olması gerektiğini, ancak MOC'nin kendisini oluşturması gerektiğini düşündüm, bu özel bir MOC ise, ilgili kuyruk henüz mevcut değil ..
János

@ János Burada aynı soru var. Onu kullanacak olan iş parçacığındaki bağlamı nasıl somutlaştırabilirsiniz? Konu henüz mevcut değil. Swift kullanıyorum ve "_importData yönteminde hareket etmenin" ne anlama geldiğini anlamıyorum.
Todanley

0

Kayıtların ve kayıtların tablo görünümüne aktarılması üzerinde çalışıyordum. Arka planda kaydı kaydetmeye çalıştığımda aynı sorunla karşılaştım

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

zaten bir PrivateQueueContext oluştururken. Yukarıdaki kodu aşağıdaki ile değiştirin

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

Rekoru kaydetmek için zaten bir privateQueueConcurrencyType oluştururken arka plan iş parçacığına kaydetmek gerçekten aptalca işimdi.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.