multithreading - executeFetchRequest上的“集合在枚舉時發生了變化”




cocoa core-data (3)

我正在導入記錄並在tableview中顯示記錄。 當我試圖在backgroundThread上保存記錄時遇到同樣的問題,如下所示

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

而我已經創建了一個PrivateQueueContext。 只需用上面的代碼替換下面的代碼

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

真的,我已經創建了一個用於保存記錄的privateQueueConcurrencyType,這是我在後台線程上保存的愚蠢工作。

我被困在一個數小時的問題上,並且已經閱讀了關於這個在上的一切(並且應用了所有發現的意見),現在我正式需要幫助。 ; O)

這裡是上下文:

在我的iPhone項目中,我需要在後台導入數據並將其插入託管對像上下文中。 遵循在這裡找到的建議,這裡是我正在做的事情:

  • 保存主要的moc
  • 使用主moc使用的持久存儲協調器實例化後台moc
  • 將我的控制器註冊為背景moc的NSManagedObjectContextDidSaveNotification通知的觀察者
  • 在後台線程上調用導入方法
  • 每次收到數據時,都將其插入後台moc
  • 一旦所有的數據已被導入,保存背景moc
  • 將更改合併到主線程中的主moc中
  • 取消註冊我的控制器作為通知的觀察者
  • 重置並釋放背景moc

有時(和隨機),例外......

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

當我在後台moc上調用executeFetchRequest時,會引發...來檢查導入的數據是否已經存在於數據庫中。 我想知道什麼是變異的設置,因為沒有任何東西在導入方法之外運行。

我已經包含了我的控制器和測試實體的全部代碼(我的項目由這兩個類和未經修改的應用程序委託組成):

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

好的,我想我已經解決了我的問題,我必須感謝Fred McCann's的這篇博客文章:

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

這個問題似乎來自我在主線程而不是後台線程上實例化我的背景moc的事實。 當Apple告訴每個線程需要擁有自己的moc時,你必須認真對待它:每個moc必須在將要使用它的線程中實例化!

移動以下幾行...

// We instantiate the background moc

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

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

...在_importData方法中(就在將控制器註冊為通知的觀察者之前)解決了這個問題。

謝謝你的幫助,彼得。 感謝Fred McCann的寶貴博客文章!


因為這種新方法可以幫助您自動完成,切換語句,更好,分別更精確的警告,...

堅持用宏...

typedef NS_ENUM( NSUInteger, CarType ) {
  FourDoorCarType,
  TwoDoorCarType
};

...請閱讀此示例https://.com/a/3190470/581190

NSInteger,...你想要什麼類型?





multithreading cocoa core-data