2012-12-26 10 views
27

I blocchi vengono acquisiti nei dati principali. Io davvero non capisco la ragione. Perché sto creando un MOC in background mentre sto elaborando un thread in background. Qui di seguito potete vedere ciò che una traccia dello stack (sto facendo una pausa l'esecuzione delle app) assomiglia a quando questo accade:Blocchi di dati principali in thread di sfondo

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

Nel EventKitHelperSyncSerialBackgroundQueue sto facendo un po 'di elaborazione Core Data in una coda di sfondo. Reminder s sono NSManagedObject s. Ci scusiamo per la quantità di codice, ma ho pensato che fosse meglio non tralasciare dettagli importanti.

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

Come stai creando il CoreDataHelper sharedInstance? –

risposta

20

Non del tutto sicuro se questo vale per voi, ma mi è stato sempre errori simili. Li risolto da

  1. Uso NSPrivateQueueConcurrencyType non NSConfinementConcurrencyType prendere elaborazione il filo principale.

  2. Mettere executeFetchRequest all'interno del MOC performBlockAndWait.

Così, nel metodo di fetchEntity CoreDataHelper.m avreste qualcosa di simile:

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

Stavo per aggiungere questo in una modifica prima di scrivere ;-) Dopo alcuni tentativi ed errori su me stesso sono passato a 'NSPrivateQueueConcurrencyType' e ora non ho più problemi. Anche se non riesco ancora a capire perché ho avuto problemi con 'NSConfinementConcurrencyType' –

+7

La tua richiesta di recupero viene passata al padre che viene eseguito su un'altra coda principale del thread (NSMainQueueConcurrencyType). Poiché NSConfinementConcurrencyType deve essere sulla stessa coda, si ottiene l'errore. Secondo le note di rilascio di iOS5, NSConfinementConcurrencyType non può essere annidato a un genitore (leggi in Contesti oggetti gestiti nidificati). http://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/_index.html –

+1

Utilizziamo 'NSPrivateQueueConcurrencyType' nei thread in background e abbiamo ancora questo problema. Penso che avvolgere i recuperi in '-performBlockAndWait:' potrebbe essere ancora necessario. –

0

questo potrebbe non dare il quadro completo, ma questo è l'approccio che ho preso

Ogni oggetto gestito richiede il suo thread. Sulla stessa discussione è possibile utilizzare lo stesso oggetto gestito più e più volte. Ma non sulla stessa coda. (questo era un equivoco su cui mi sono soffermato) la coda di sfondo può avere molti thread diversi e il MOC deve essere univoco per ogni thread.

ecco il metodo che ho usato.

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

Hai creato un oggetto che mantiene una mappatura di thread noti e MOC e restituisce il MOC corretto per il thread corrente. Lo capisco correttamente? In tal caso, qual è il vantaggio di questa soluzione, ad esempio, di Alex L's 'NSPrivateQueueConcurrencyType'? (Rif: http://developer.apple.com/library/mac/releasenotes/DataManagement/RN-CoreData/_index.html#//apple_ref/doc/uid/TP40010637-CH1-DontLinkElementID_1) –

+0

Bene, per il meglio della mia comprensione, segue la convenzione consigliata dagli autori dell'infrastruttura di base dei dati. E mi è stato detto che la documentazione è (o forse lo è stata.) Errata nel presupporre che una singola "coda" usi lo stesso MOC quando in realtà è un singolo thread di cui una singola coda può avere molti. Questo ti consente non solo di usare più thread, ma se guardi. inoltre notifica a tutti i thread esistenti che qualcosa è cambiato. Che ti consentirà di rispondere a tali cambiamenti (NSFetchedResultsController risponde a queste modifiche) –

Problemi correlati