Search code examples
objective-cmultithreadingcore-datathread-safetynsmanagedobjectcontext

NSManagedObjectContext crashing when accessed on external thread


I'm currently having a threading issue with the managedObjectContext within my application. Currently, I have a background thread running that MUST be in the background, but accesses the managedObjectContext at the same time. Another ViewController calls on the method processAllApplications shown below that then calls checkCompletedApplicationsFor24HourExpiration which then calls getAppsWithStatus. The thread seems to be currently locked causing this operation to halt where the warning below is. I need a way to process this through and am quite a noob when it comes to Core Data. Would anyone be able to advise. I was reading that I may have to create multiple instances of my managedObject and merge them. How would I go about that if that is the case?

AppDelegate:

- (NSManagedObjectContext *)managedObjectContext
{

    [__managedObjectContext lock];
    if (__managedObjectContext != nil) {

        [__managedObjectContext unlock];
        return __managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        __managedObjectContext = [[NSManagedObjectContext alloc] init];
        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
    }

    [__managedObjectContext unlock];
    return __managedObjectContext;

}
    - (NSMutableArray*)getAppsWithStatus:(int)intStatus {


    NSLog(@"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Application" inManagedObjectContext:self.managedObjectContext];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];

    // Set example predicate and sort orderings...
    NSNumber *status = [NSNumber numberWithInt:intStatus];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"status = %@ && username = %@", status, [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]];
    #warning FAILS HERE INTO ABYSS
    [request setPredicate:predicate];

    NSError *error = nil;

    NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[self.managedObjectContext executeFetchRequest:request error:&error]];
    for (Application* eachApp in applications)
        eachApp.applicationNumber = nil;
    [self saveDB];
    return applications;
}

    - (void)processAllApplications:(id)userInfo {
    [self.processApplicationsLock lock];

    if ([[NSUserDefaults standardUserDefaults] objectForKey:@"username"] == nil) return;    // Not logged in
    NSLog(@"processing");
    [self checkCompletedApplicationsFor24HourExpiration];
    [self alertFor12HourCompletedApplications];
    [self alertForExpiredDraftApplications];

    if ([DeleteAllDraftApplicationsForCurrentApplicationYear isSatisifiedByDate:[DateTimeFactory currentApplicationDate]]) {
        [self deleteExpiredApps];
    }

    [self performSelector:@selector(sendApplications:) withObject:nil afterDelay:3];

    [self.processApplicationsLock unlock];
}

- (void)checkCompletedApplicationsFor24HourExpiration {

    NSLog(@"OutboxSender - (void)checkCompletedApplicationsFor24HourExpiration");
    NSLog(@"%i on main thread checkCompletedApplicationsFor24HourExpiration", [NSThread currentThread].isMainThread);
    NSArray* completedApps = [self getAppsWithStatus:STATUS_COMPLETED];
    NSDate* targetDate = [self offsetDate:[DateTimeFactory currentApplicationDate] withDay:-1 withMonth:0 withHour:0];

    for (Application* theApplication in completedApps) {
        if ([MoveCompletedApplicationToDraftApplicationSpec isSatisfiedByApplication:theApplication cutOffDate:targetDate]) {
            NSLog(@"Sending To draft with date: %@", theApplication.submittedDate);
            theApplication.status = [NSNumber numberWithInt:STATUS_DRAFT];
            [self deleteSignatures:theApplication];
        }
    }

    NSString* message = [NSString stringWithFormat:@"%i completed application/s have been sent to drafts", [completedApps count]];
    echo_Alert(@"", message);

    [self saveDB];
}

Solution

  • create separate managed object context
    
    +(NSManagedObjectContext *)getManagedObjectContext
    {
        NSManagedObjectContext *managedObjectContext;
        @try {
            NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
            if (coordinator != nil) {
                managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
                [managedObjectContext setPersistentStoreCoordinator: coordinator];
            }
    
        }
        @catch (NSException *exception) {
            NSLog(@"Exception occur %@",exception);
        }
        return managedObjectContext;
    
    Use this separate managed object context in your fetching method,
    
        - (NSMutableArray*)getAppsWithStatus:(int)intStatus {
    
    NSMutableArray * mutableObjects;
        NSLog(@"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
        NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Application" inManagedObjectContext:[self getManagedObjectContext]]; // Here use separate managed object context
        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        [request setEntity:entityDescription];
    
        // Set example predicate and sort orderings...
        NSNumber *status = [NSNumber numberWithInt:intStatus];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"status = %@ && username = %@", status, [[NSUserDefaults standardUserDefaults] objectForKey:@"username"]];
        #warning FAILS HERE INTO ABYSS
        [request setPredicate:predicate];
    
        NSError *error = nil;
    
        NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[[self getManagedObjectContext] executeFetchRequest:request error:&error]];
    
     NSMutableArray * resultedArray = [applications mutableCopy];
            NSMutableArray * objectIds = [[NSMutableArray alloc] initWithCapacity:[resultedArray count]];
            for (NSManagedObject *obj in resultedArray) {
                [objectIds addObject:obj.objectID];
            }
    
            mutableObjects = [[NSMutableArray alloc] initWithCapacity:[objectIds count]];
            for (NSManagedObjectID * objectID in objectIds) {
                NSManagedObject * obj = [self.managedObjectContext
     objectWithID:objectID]; // Here use self.managedObjectContext in which you already created.
                [mutableObjects addObject:obj];
            }
    
        for (Application* eachApp in mutableObjects)
            eachApp.applicationNumber = nil;
        [self saveDB];
        return mutableObjects;
    }