ios - Bloqueos de datos centrales en hilos de fondo
cocoa-touch cocoa (2)
No estoy totalmente seguro de si esto se aplica a usted, pero estaba recibiendo errores similares. Los resolví por
Usar
NSPrivateQueueConcurrencyType
noNSConfinementConcurrencyType
para quitar el procesamiento del hilo principal.Poniendo
executeFetchRequest
dentro de MOC''sperformBlockAndWait
.
Entonces, en el método fetchEntity de CoreDataHelper.m, tendrías algo como esto:
[context performBlockAndWait:^{
NSError *error = nil;
NSArray *entities = [context executeFetchRequest:request error:&error];
}];
Me estoy bloqueando en Core Data. Realmente no entiendo la razón. Porque estoy creando un MOC de fondo cuando estoy procesando en un hilo de fondo. A continuación puede ver a qué se parece un seguimiento de pila (estoy pausando la ejecución de la aplicación) cuando esto sucede:
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 ()
En EventKitHelperSyncSerialBackgroundQueue
, estoy procesando algunos Core Data en una cola de segundo plano. Reminder
son NSManagedObject
s. Perdón por la cantidad de código, pero pensé que era mejor no omitir ningún detalle importante.
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();
}
}];
}
}
}
esto puede no dar la imagen completa, pero este es el enfoque que tomé
Todo objeto gestionado requiere su propio hilo. En el mismo hilo, puede usar el mismo objeto administrado una y otra vez. Pero no en la misma fila. (esta fue una idea errónea sobre la que me he apoyado) la cola de fondo puede tener muchos hilos diferentes y el MOC debe ser único para cada hilo.
aquí está el método que utilicé.
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