Saya terjebak pada masalah selama berjam-jam sekarang dan setelah membaca semua tentang ini di stackoverflow (dan menerapkan setiap saran yang ditemukan), saya sekarang secara resmi membutuhkan bantuan. ;Hai)
Inilah konteksnya:
Dalam proyek iPhone saya, saya perlu mengimpor data di latar belakang dan memasukkannya ke dalam konteks objek yang dikelola. Mengikuti saran yang ditemukan di sini, inilah yang saya lakukan:
- Simpan moc utama
- Membuat instance moc latar belakang dengan koordinator penyimpanan tetap yang digunakan oleh moc utama
- Daftarkan pengontrol saya sebagai pengamat notifikasi NSManagedObjectContextDidSaveNotification untuk background moc
- Panggil metode impor pada utas latar belakang
- Setiap kali data diterima, masukkan pada background moc
- Setelah semua data diimpor, simpan moc latar belakang
- Gabungkan perubahan menjadi moc utama, di utas utama
- Batalkan pendaftaran pengontrol saya sebagai pengamat untuk notifikasi
- Setel ulang dan lepaskan moc latar belakang
Terkadang (dan secara acak), pengecualian ...
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...
... dilempar ketika saya memanggil executeFetchRequest di background moc, untuk memeriksa apakah data yang diimpor sudah ada di database. Saya ingin tahu apa yang mengubah set karena tidak ada yang berjalan di luar metode impor.
Saya telah menyertakan seluruh kode pengontrol saya dan entitas pengujian saya (proyek saya terdiri dari dua kelas ini dan delegasi aplikasi, yang telah tidak diubah):
//
// 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
Ini semuanya! Seluruh proyek ada di sini. Tidak ada tampilan tabel, tidak ada NSFetchedResultsController, tidak ada yang lain selain utas latar belakang yang mengimpor data pada moc latar belakang.
Apa yang bisa mengubah himpunan dalam kasus ini?
Saya cukup yakin saya kehilangan sesuatu yang jelas dan itu membuat saya gila.
EDIT:
Berikut adalah pelacakan tumpukan lengkap:
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'
sumber
Jawaban:
Oke, saya pikir saya telah menyelesaikan masalah saya dan saya harus berterima kasih pada posting blog ini dari Fred McCann:
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
Masalahnya tampaknya berasal dari fakta bahwa saya memberi contoh moc latar belakang saya di utas utama, bukan utas latar belakang. Ketika Apple memberi tahu bahwa setiap utas harus memiliki moc sendiri, Anda harus menganggapnya serius: setiap moc harus dibuat di utas yang akan menggunakannya!
Memindahkan baris berikut ...
... dalam metode _importData (sebelum mendaftarkan pengontrol sebagai pengamat untuk notifikasi) memecahkan masalah.
Terima kasih atas bantuan Anda, Peter. Dan terima kasih kepada Fred McCann untuk entri blognya yang berharga!
sumber
each moc must be instantiated in the thread that will be using it
Saya meskipun hanya operasi di MOC yang harus berada di utas yang sama, tetapi membuat MOC itu sendiri juga, jika ini MOC pribadi, antrean terkait belum ada ..Saya sedang mengerjakan mengimpor catatan & menampilkan catatan dalam tampilan tabel. Menghadapi masalah yang sama ketika saya mencoba menyimpan catatan di backgroundThread seperti di bawah ini
sementara saya sudah membuat PrivateQueueContext. Cukup ganti kode di atas dengan yang di bawah ini
Sungguh, pekerjaan bodoh saya adalah menyimpan di utas latar belakang sementara saya sudah membuat privateQueueConcurrencyType untuk menyimpan catatan.
sumber