multithreading - "تم تحوير المجموعة أثناء تعدادها" على executeFetchRequest




cocoa core-data (3)

حسنًا ، أعتقد أنني قمت بحل مشكلتي ويجب أن أشكر هذا المنشور من Fred Makann's:

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

يبدو أن المشكلة تأتي من حقيقة أنني إنشاء مثيل بلدي الخلفية على موضوع الرئيسي بدلا من مؤشر ترابط الخلفية. عندما تخبر Apple أن كل موضوع يجب أن يكون له moc خاص به ، يجب أن تأخذ الأمر على محمل الجد: يجب أن يتم إنشاء كل moc في الخيط الذي سيستخدمه!

تحريك السطور التالية ...

// We instantiate the background moc

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

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

... في طريقة _importData (فقط قبل تسجيل وحدة التحكم كمراقب للإشعار) يحل المشكلة.

شكرا لمساعدتكم ، بيتر. وشكرا لفريد ماكان لمنصته بلوق قيمة!

أنا عالق في مشكلة لساعات حتى الآن وبعد قراءة كل شيء عن هذا على (وتطبيق كل النصائح وجدت) ، أنا الآن في حاجة رسمية للمساعدة. ؛ س)

هذا هو السياق:

في مشروع iPhone ، أحتاج إلى استيراد البيانات على الخلفية وإدراجه في سياق كائن مُدار. بعد النصائح الموجودة هنا ، إليك ما أفعله:

  • حفظ moc الرئيسي
  • Instantiate moc خلفية مع منسق المخزن المستمر المستخدمة من قبل moc الرئيسي
  • تسجيل جهاز التحكم الخاص بي كمراقب لإشعار NSManagedObjectContextDidSaveNotification للواسطة الخلفية
  • استدعاء أسلوب الاستيراد على مؤشر ترابط الخلفية
  • في كل مرة يتم تلقي البيانات ، أدخله على الخلفية
  • بمجرد استيراد كل البيانات ، احفظ 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'

كنت أعمل على استيراد سجل وعرض السجلات في tableview. واجه نفس المشكلة عندما حاولت حفظ السجل في backgroundThread مثل أدناه

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

بينما قمت بالفعل بإنشاء PrivateQueueContext. ما عليك سوى استبدال الرمز الوارد أدناه بأقل من واحد

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

حقا كان عملي أحمق للحفظ على مؤشر ترابط الخلفية بينما كنت بالفعل إنشاء privateQueueConcurrencyType لحفظ السجل.


تم تحديث فئة Reachability فقط. يمكنك الآن استخدام:

Reachability* reachability = [Reachability reachabilityWithHostName:@"www.apple.com"];
NetworkStatus remoteHostStatus = [reachability currentReachabilityStatus];

if (remoteHostStatus == NotReachable) { NSLog(@"not reachable");}
else if (remoteHostStatus == ReachableViaWWAN) { NSLog(@"reachable via wwan");}
else if (remoteHostStatus == ReachableViaWiFi) { NSLog(@"reachable via wifi");}




multithreading cocoa core-data