27

Core Data でロックがかかっています。理由が本当にわかりません。バックグラウンドスレッドで処理しているときにバックグラウンドMOCを作成しているためです。以下に、これが発生したときのスタック トレース (アプリの実行を一時停止しています) がどのように見えるかを示します。

Thread 1, Queue : com.apple.main-thread

#0  0x32d2a0fc in __psynch_mutexwait ()
#1  0x3608b128 in pthread_mutex_lock ()
#2  0x365d2dac in -[_PFLock lock] ()
#3  0x365e3264 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#4  0x365e1e2a in -[NSManagedObjectContext executeFetchRequest:error:] ()
#5  0x3664a93e in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#6  0x3664b0c8 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke_0 ()
#7  0x3932bd28 in _dispatch_barrier_sync_f_slow_invoke ()


Thread 10, Queue : EventKitHelperSyncSerialBackgroundQueue

#0  0x32d19f04 in semaphore_wait_trap ()
#1  0x3932c300 in _dispatch_thread_semaphore_wait$VARIANT$mp ()
#2  0x3932a880 in _dispatch_barrier_sync_f_slow ()
#3  0x3663b9e6 in _perform ()
#4  0x3664adba in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#5  0x365e1e2a in -[NSManagedObjectContext executeFetchRequest:error:] ()
#6  0x000b11e4 in -[CoreDataHelper fetchEntity:predicate:andSortDescriptors:inManagedObjectContext:] at /Users/peterwarbo/Desktop/app/CoreDataHelper.m:110
#7  0x000ad648 in -[EventKitHelper processChangedCalendar] at /Users/peterwarbo/Desktop/app/EventKitHelper.m:242
#8  0x000ad3b4 in __54-[EventKitHelper syncInBackgroundWithCompletionBlock:]_block_invoke_0 at /Users/peterwarbo/Desktop/app/EventKitHelper.m:218
#9  0x3932711e in _dispatch_call_block_and_release ()
#10 0x3932aece in _dispatch_queue_drain$VARIANT$mp ()
#11 0x3932adc0 in _dispatch_queue_invoke$VARIANT$mp ()
#12 0x3932b91c in _dispatch_root_queue_drain ()
#13 0x3932bac0 in _dispatch_worker_thread2 ()
#14 0x36090a10 in _pthread_wqthread ()
#15 0x360908a4 in start_wqthread ()

ではEventKitHelperSyncSerialBackgroundQueue、バックグラウンド キューで Core Data 処理を行っています。Reminders はNSManagedObjects です。コードの量が多くて申し訳ありませんが、重要な詳細を省略しない方がよいと考えました。

EventKitHelper.m

- (void)syncInBackgroundWithCompletionBlock:(CalendarSyncCompletionBlock)block {

    DLogName()

    self.completionBlock = block;

    if (self.syncSerialBackgroundQueue == NULL) {
        self.syncSerialBackgroundQueue = dispatch_queue_create("EventKitHelperSyncSerialBackgroundQueue", 0);
    }

    dispatch_async(self.syncSerialBackgroundQueue, ^{

        [self processChangedCalendar];
    });
}

- (void)processChangedCalendar {

    DLogName()

    CoreDataHelper *cdHelper = [CoreDataHelper sharedInstance];

    // Store has been changed, events could be updated/deleted/added
    // Need to check if any of the user created Reminders are referencing the calendar
    // If so, update the affected Reminders

    // Predicate to fetch only Reminders that are of type (RMReminderDateServiceCalendarEvent or RMReminderDateServiceCalendarBirthday) AND status is not completed
    NSPredicate *userRemindersPredicate = [NSPredicate predicateWithFormat:@"(dateService == %@ OR dateService == %@) AND status != %@", @(RMReminderDateServiceCalendarEvent), @(RMReminderDateServiceCalendarBirthday), @(RMReminderStatusCompleted)];

    // Sort the user's Reminders with the earliest date first
    NSSortDescriptor *dateSortAsc = [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES];

    // Creating a new MOC for thread safety
    NSManagedObjectContext *syncContext = [cdHelper threadedManagedObjectContext];
    self.syncContext = syncContext;

    NSArray *usersReminders = [[CoreDataHelper sharedInstance] fetchEntity:APReminderEntity predicate:userRemindersPredicate andSortDescriptors:@[dateSortAsc] inManagedObjectContext:syncContext];

    if (usersReminders.count == 0) {

        DLog(@"User doesn't have any Calendar Reminders, no need to sync")

        BOOL error = NO;

        self.completionBlock(error);

        return;

    } else {

        if (!self.isCalendarAccessAuthorized) {

            DLog(@"Calendar access is not authorized and we have Calendar Reminders, alert the user")

            BOOL error = YES;

            self.completionBlock(error);

            return;

        } else {

            DLog(@"Calendar access is authorized")
        }
    }

    if (!self.calendarchanged) {

        DLog(@"Calendar not updated, no need to sync")

        BOOL error = NO;

        self.completionBlock(error);

        return;
    }

    DLog(@"Calendar updated, syncing...")

    NSDate *earliestReminderDate = [(Reminder *) [usersReminders objectAtIndex:0] date];

    // Since there exists a possibility that a Calendar event can change date back in time, we should fetch events from our earliest Reminder date + 1 year back

    NSDate *eventsFromThisDate = [Utilities oneYearAgoForDate:[Utilities midnightDateForDate:earliestReminderDate]];

    NSDate *endDate = [NSDate distantFuture]; // This will get me events 4 years from now

    // Create the predicate
    NSPredicate *eventStorePredicate = [self.eventStore predicateForEventsWithStartDate:eventsFromThisDate endDate:endDate calendars:nil];

    // Fetch all events that match the predicate.
    NSArray *eventKitEvents = [self.eventStore eventsMatchingPredicate:eventStorePredicate];

    NSMutableArray *events = [NSMutableArray arrayWithCapacity:100];

    for (EKEvent *event in eventKitEvents) {

        NSString *eventTitle = [event title];
        NSDate *eventDate = [event startDate];
        NSDate *eventDateModified = [event lastModifiedDate];
        NSString *eventID = [event eventIdentifier];

        // Check if event is a Birthday event
        BOOL isBirthday = [event birthdayPersonID] != -1 ? YES : NO;

        RMReminderDateService dateService;

        if (isBirthday) {

            dateService = RMReminderDateServiceCalendarBirthday;

        } else {

            dateService = RMReminderDateServiceCalendarEvent;
        }

        RMDateEvent *calendarEvent = [[RMDateEvent alloc] initWithDate:eventDate
                                                          dateModified:eventDateModified
                                                                  name:eventTitle
                                                           dateService:dateService
                                                                 andID:eventID];

        BOOL eventAlreadyAdded = NO;

        if (!eventAlreadyAdded) {

            [events addObject:calendarEvent];
        }
    }

    for (Reminder *reminder in usersReminders) {

        NSPredicate *predicateID = [NSPredicate predicateWithFormat:@"ID == %@", reminder.dateServiceID];
        NSArray *eventsMatchingID = [events filteredArrayUsingPredicate:predicateID];

        RMDateEvent *event = [eventsMatchingID lastObject];

        if (event == nil) {

            // We couldn't find the event by ID, try to find it by date AND title

            NSPredicate *predicateDateAndTitle = [NSPredicate predicateWithFormat:@"date == %@ AND name == %@", reminder.date, reminder.dateText];

            NSArray *eventsMatchingDateAndTitle = [events filteredArrayUsingPredicate:predicateDateAndTitle];

            event = [eventsMatchingDateAndTitle lastObject];

            if (event == nil) {

                // We couldn't find the event, most likely it has been deleted from the user's events or the user has changed all values for our saved event :-(

            } else {

                // We found it by date AND title                
                [self processReminder:reminder forDateEvent:event];
            }

        } else {

            // We found it by ID
            [self processReminder:reminder forDateEvent:event];
        }
    }

    [self fetchEventsFromNow];
    [self processEventKitEvents];

    #warning TODO: Broadcast a message to update the Reminder date
    AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
    [appDelegate setTabCountInBackground];

    self.calendarchanged = NO;

    DLog(@"Calendar sync done")

    BOOL error = NO;

    self.completionBlock(error);
}

- (void)processReminder:(Reminder *)reminder forDateEvent:(RMDateEvent *)event {

    NSDate *eventModifiedDate = [event dateModified];

    if ([eventModifiedDate compare:reminder.dateModified] == NSOrderedDescending) {

        // This event has been modified
        // Most important now is to check if the changed event date has passed
        NSDate *today = [NSDate date];

        if ([today compare:event.date] == NSOrderedDescending) {

            // Event date has passed

            if (reminder.isRepeating) {

                // We cancel the UILocalNotification and reschedule a new UILocalNotification for the next Reminder date status also set to overdue

                NSDate *reminderDate = [Utilities reminderDateFromDate:event.date andTime:reminder.date];

                // Cancel UILocalNotification
                [Utilities cancelUILocalNotificationForReminder:reminder];

                reminder.status = @(RMReminderStatusOverdue);

                reminder.date = reminderDate;
                reminder.dateModified = event.dateModified;
                reminder.dateServiceID = event.ID;
                reminder.dateText = event.name;

                NSDate *nextReminderDate = [Utilities nextReminderDateFromNowForReminder:reminder];
                reminder.date = nextReminderDate;

                // Re-schedule the Reminder
                [Utilities scheduleUILocalNotificationForReminder:reminder];

                // We change back to this old Reminder date to reflect the overdue status
                reminder.date = reminderDate;

                [[CoreDataHelper sharedInstance] saveInManagedObjectContext:self.syncContext];


            } else {

                // We should cancel the UILocalNotification for this Reminder and set the status for this Reminder to overdue

                NSDate *reminderDate = [Utilities reminderDateFromDate:event.date andTime:reminder.date];

                // Cancel UILocalNotification
                [Utilities cancelUILocalNotificationForReminder:reminder];

                reminder.status = @(RMReminderStatusOverdue);

                reminder.date = reminderDate;
                reminder.dateModified = event.dateModified;
                reminder.dateServiceID = event.ID;
                reminder.dateText = event.name;

                [[CoreDataHelper sharedInstance] saveInManagedObjectContext:self.syncContext];
            }

        } else {

            // Event date is in the future

            NSDate *reminderDate = [Utilities reminderDateFromDate:event.date andTime:reminder.date];

            // Cancel UILocalNotification
            [Utilities cancelUILocalNotificationForReminder:reminder];

            reminder.status = @(RMReminderStatusUpcoming);

            reminder.date = reminderDate;
            reminder.dateModified = event.dateModified;
            reminder.dateServiceID = event.ID;
            reminder.dateText = event.name;

            [[CoreDataHelper sharedInstance] saveInManagedObjectContext:self.syncContext];

            // Re-schedule the Reminder
            [Utilities scheduleUILocalNotificationForReminder:reminder];
        }
    }
}

CoreDataHelper.m

- (NSArray *)fetchEntity:(NSString *)entity predicate:(NSPredicate *)predicate andSortDescriptors:(NSArray *)sortDescriptors inManagedObjectContext:(NSManagedObjectContext *)context {

    DLogName()

    if (context == nil) {

        // Use default MOC
        context = self.managedObjectContext;
    }

    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:entity inManagedObjectContext:context];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];

    if (predicate != nil) {

        [request setPredicate:predicate];
    }

    if (sortDescriptors != nil) {

        [request setSortDescriptors:sortDescriptors];
    }

    NSError *error = nil;
    NSArray *entities = [context executeFetchRequest:request error:&error];

    if (entities == nil) {

        DLog(@"There was an error: %@", [error userInfo]);
    }

    return entities;
}


- (NSManagedObjectContext *)threadedManagedObjectContext {

    NSManagedObjectContext *threadedMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    threadedMoc.parentContext = self.managedObjectContext;

    return threadedMoc;
}

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext {

    if (_managedObjectContext != nil)
    {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        //_managedObjectContext = [[NSManagedObjectContext alloc] init];
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

- (void)saveInManagedObjectContext:(NSManagedObjectContext *)context {

    if (context == nil) {

        // Use default MOC
        context = self.managedObjectContext;

        NSError *error = nil;

        if (context != nil)
        {
            if ([context hasChanges] && ![context save:&error])
            {
                /*
                 Replace this implementation with code to handle the error appropriately.

                 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                 */
                DLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }

    } else {

        NSError *error = nil;

        // First save (child) context
        if ([context hasChanges] && ![context save:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             */
            DLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }

        // Then save parent context
        if ([self.managedObjectContext hasChanges])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             */

            [self.managedObjectContext performBlock:^{

                NSError *parentError = nil;
                [self.managedObjectContext save:&parentError];

                if (parentError) {

                    DLog(@"Unresolved error %@, %@", parentError, [parentError userInfo]);
                    abort();
                }
            }];
        }
    }
}
4

2 に答える 2

20

これがあなたに当てはまるかどうかは完全にはわかりませんが、同様のエラーが発生していました。私はそれらを解決しました

  1. メインスレッドから処理を取り外さNSPrivateQueueConcurrencyTypeないために使用します。NSConfinementConcurrencyType

  2. MOCexecuteFetchRequestの中に入れるperformBlockAndWait

したがって、CoreDataHelper.mのfetchEntityメソッドには、次のようなものがあります。

[context performBlockAndWait:^{
   NSError *error = nil;
   NSArray *entities = [context executeFetchRequest:request error:&error];
}];
于 2012-12-28T22:40:18.240 に答える
0

これは全体像を示すものではないかもしれませんが、これは私が取ったアプローチです

すべての管理対象オブジェクトには、独自のスレッドが必要です。同じスレッドで、同じ管理対象オブジェクトを何度も使用できます。しかし、同じキューにはありません。(これは私が傾倒した誤解でした)バックグラウンドキューには多くの異なるスレッドが含まれる可能性があり、MOCはスレッドごとに一意である必要があります。

これが私が使った方法です。

ManagedObjectContextHolder.h

#import <Foundation/Foundation.h>

@interface ManagedObjectContextHolder : NSObject

+ (ManagedObjectContextHolder*) threadContextHolder;

@property (nonatomic, strong) NSManagedObjectContext *context;
@property (nonatomic, strong) NSString *contextThreadMocGuid;
@property (nonatomic, weak) NSThread *contextThread;

@end

ManagedObjectContextHolder.m

#import "ManagedObjectContextHolder.h"

@interface HSContextSaveHandler : NSObject

@property (nonatomic, strong) NSArray *mocArray;

- (void) saveHappened:(NSNotification*) saveNotification;

@end

@implementation HSContextSaveHandler

@synthesize mocArray = _mocArray;

int saveFinished = 0;

- (MyAppDelegate*) appDelegete{
    return (MyAppDelegate*) [UIApplication sharedApplication].delegate;
}


- (void) saveHappened:(NSNotification *)saveNotification{
    if (saveNotification && saveNotification.userInfo){
        NSArray *staticArray = [NSArray arrayWithArray:self.mocArray];

        for (id item in staticArray) {
            if ([item isKindOfClass:[ManagedObjectContextHolder class]]) {
                ManagedObjectContextHolder *holder = item;
                if ([saveNotification object] != holder.context){
                    @try {
                        [holder.context mergeChangesFromContextDidSaveNotification:saveNotification];
                    }
                    @catch (NSException *exception) {
                        HSLogBrute(@"<<<<<<<< MERGE CHANGES FROM CONTEXT DID SAVE NOTIFICATION >>>>>>>>\n%@",saveNotification);
                    }
                }
            }
        }
        saveFinished = 3;
    }

}

@end

@interface NSThread (mocGuid)

- (NSString*) mocGuid;
- (void) setMocGuid:(NSString*) mocGuid;

@end

@implementation NSThread (mocGuid)

- (NSString *)mocGuid{
    return [self.threadDictionary valueForKey:@"mocGuid"];
}
- (void)setMocGuid:(NSString *)mocGuid{
    [self.threadDictionary setValue:mocGuid forKey:@"mocGuid"];
}

@end

@implementation ManagedObjectContextHolder

static NSMutableArray *_mocHolders;
static HSContextSaveHandler *_mocSaveHandler;
+ (NSMutableArray*) mocHolders{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _mocHolders = [NSMutableArray arrayWithCapacity:5];
        _mocSaveHandler = [[HSContextSaveHandler alloc] init];
        _mocSaveHandler.mocArray = _mocHolders;

        [[NSNotificationCenter defaultCenter] addObserver:_mocSaveHandler selector:@selector(saveHappened:) name:NSManagedObjectContextDidSaveNotification object:nil];
    });
    return _mocHolders;
}

+ (ManagedObjectContextHolder *)threadContextHolder{
    NSThread *currentThread = [NSThread currentThread];

    NSString *mocGuid = currentThread.mocGuid;

    ManagedObjectContextHolder *result = nil;

    NSMutableArray *removeList = [[NSMutableArray alloc] initWithCapacity:[self mocHolders].count];
    NSLog(@"Context Holders Count %d",[self mocHolders].count);
    for (ManagedObjectContextHolder *item in [self mocHolders]) {
        if (mocGuid != nil && item.contextThread == currentThread && item.contextThreadMocGuid == currentThread.mocGuid){
            result = item;
        }

        if (item.contextThread == nil) {
            [removeList addObject:item];
        }
    }

    if (removeList.count > 0){
        NSLog(@"Removing %d Context Holders for Nil Threads",removeList.count);
        [[self mocHolders] removeObjectsInArray:removeList];
    }
    if (result == nil){
        result = [[ManagedObjectContextHolder alloc] init];
        result.contextThread = currentThread;

        if (mocGuid == nil){
            mocGuid = [HSStaticContainer uuidAsShortString];
            currentThread.mocGuid = mocGuid;
        }

        result.contextThreadMocGuid = mocGuid;
        result.context = [[NSManagedObjectContext alloc] init];
        [result.contextcontext setPersistentStoreCoordinator:[self appDelegate].persistentStoreCoordinator];
        [result.context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [[self mocHolders] addObject:result];
    }
    return result;

}

@synthesize context = _context;
@synthesize contextThreadMocGuid = _contextThreadMocGuid;
@synthesize contextThread = _contextThread;

- (id)init{
    self = [super init];
    if (self) {
        NSLog(@"Creating a Managed Object Context Holder. Here is the Stack Trace.\r\r%@",[NSThread callStackSymbols]);
    }
    return self;
}

@end
于 2012-12-29T01:21:31.607 に答える