2012-12-26 9 views
27

Dostaję blokady w danych podstawowych. Naprawdę nie rozumiem powodu. Ponieważ tworzę tło MOC podczas przetwarzania w wątku tła. Poniżej można zobaczyć ślad stosu (jestem wstrzymując wykonanie aplikacji) wygląda kiedy tak się dzieje:Podstawowe blokady danych w wątkach w tle

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() 

W EventKitHelperSyncSerialBackgroundQueue robię pewne przetwarzanie danych Core w kolejce tła. Reminder s są NSManagedObject s. Przepraszam za ilość kodu, ale pomyślałem, że lepiej nie pomijać żadnych ważnych szczegółów.

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(); 
       } 
      }]; 
     } 
    } 
} 
+0

Jak się tworzenie CoreDataHelper sharedInstance? –

Odpowiedz

20

nie całkowicie pewien, czy to dotyczy Ciebie, ale byłem coraz podobnych błędów. Rozwiązałem je

  1. Korzystanie NSPrivateQueueConcurrencyType nie NSConfinementConcurrencyType wziąć przetwarzanie wyłączyć główny wątek.

  2. Wprowadzenie executeFetchRequest do wnętrza MOC performBlockAndWait.

więc w metodzie fetchEntity CoreDataHelper.m za to masz coś takiego:

[context performBlockAndWait:^{ 
    NSError *error = nil; 
    NSArray *entities = [context executeFetchRequest:request error:&error]; 
}]; 
+0

Już miałem dodać to w edycji przed napisaniem ;-) Po pewnym okresie prób i błędów zmieniłem na 'NSPrivateQueueConcurrencyType', a teraz już nie mam żadnych problemów. Chociaż nadal nie rozumiem, dlaczego miałem problemy z 'NSConfinementConcurrencyType' –

+7

Twoje żądanie pobierania jest przekazywane do jednostki nadrzędnej, która działa w kolejce głównej innego wątku (NSMainQueueConcurrencyType). Ponieważ NSConfinementConcurrencyType musi znajdować się w tej samej kolejce, pojawia się błąd. Zgodnie z uwagami do wydania iOS5, NSConfinementConcurrencyType nie może być zagnieżdżony do elementu nadrzędnego (odczyt w kontekście zagnieżdżonych obiektów zarządzanych). http://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/_index.html –

+1

Używamy 'NSPrivateQueueConcurrencyType' na naszych wątkach w tle i nadal mamy ten problem. Myślę, że owijanie pobrań w '-performBlockAndWait:' może nadal być konieczne. –

0

może to nie daje pełnego obrazu, ale jest to podejście Wziąłem

Każdy obiekt wymaga udało własny wątek. W tym samym wątku można wielokrotnie używać tego samego obiektu zarządzanego. Ale nie w tej samej kolejce. (było to błędne przekonanie, o którym się opieram) kolejka tła może mieć wiele różnych wątków, a MOC musi być unikalny dla każdego wątku.

Oto metoda, której użyłem.

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 
+0

Utworzono obiekt, który zachowuje odwzorowanie znanych wątków i MOC-ów i zwraca poprawny MOC dla bieżącego wątku. Czy rozumiem to poprawnie? Jeśli tak, to jaka jest zaleta tego rozwiązania w porównaniu z, powiedzmy, rozwiązaniem Alexa 'NSPrivateQueueConcurrencyType'? (Ref: http://developer.apple.com/library/mac/releasenotes/DataManagement/RN-CoreData/_index.html#//apple_ref/doc/uid/TP40010637-CH1-DontLinkElementID_1) –

+0

Cóż to, do najlepszych mojego zrozumienia, przestrzega konwencji zalecanej przez autorów podstawowej infrastruktury danych. I zostałem poinformowany, że dokumentacja jest (a może była) niepoprawna w założeniu, że jedna "Kolejka" używa tego samego MOC-a, podczas gdy w rzeczywistości jest to pojedynczy wątek, którego jedna kolejka może mieć wiele. Pozwala to nie tylko używać wielu wątków, ale także wygląd. powiadamia również o wszystkich istniejących wątkach, które coś zmieniły. Który pozwoli ci odpowiedzieć na te zmiany (NSFetchedResultsController reaguje na te zmiany) –