0

I am implementing CoreData stack according to https://stackoverflow.com/a/24663533 (option A from image) but it works in an unexpected way.

I have rootContext (NSPrivateQueueConcurrencyType), it has 2 children: uiContext (NSMainQueueConcurrencyType) for objects fetching and syncContext (NSPrivateQueueConcurrencyType) for asynchronous data editing.

As I thought, when I save something in syncContext in performBlock (background queue), changes will be propagated to rootContext, but uiContext will not be changed until I observe NSManagedObjectContextDidSaveNotification and merge changes from notification. But changes are reflected immediately after syncContext save.

My first question is: why is uiContext updated without manual merge?

My second question: why is rootContext modified on background (not on main thread) after syncContext save? Some time ago I asked question about "CoreData could not fulfill a fault" problem with MagicalRecord 'CoreData could not fulfill a fault' error with MagicalRecord but I didn't receive answer, so I decided to find solution without external libraries.

It seems, that main thread is reading object properties and the same object is deleted on background whilst operators on main thread still don't return control.

Here is my source code:

#import <CoreData/CoreData.h>
#import "DataLayer.h"
#import "Person.h"

@interface DataLayer ()

@property (nonatomic, strong) NSManagedObjectModel *model;
@property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator;

@property (nonatomic, strong) NSManagedObjectContext *rootContext;
@property (nonatomic, strong) NSManagedObjectContext *uiContext;
@property (nonatomic, strong) NSManagedObjectContext *syncContext;

@end

@implementation DataLayer

+ (void)load
{
    [self instance];
}

+ (DataLayer *)instance
{
    static DataLayer *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[DataLayer alloc] init];
    });
    return instance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {

        [self initModel];
        [self initCoordinator];
        [self initContexts];
        [self observeContextSaveNotification];

        [self startTesting];
    }
    return self;
}

- (void)initModel
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    self.model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}

- (void)initCoordinator
{
    NSURL *directory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *storeURL = [directory URLByAppendingPathComponent:@"Model.sqlite"];
    NSError *error = nil;
    self.coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
    if (![self.coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

- (void)initContexts
{
    self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.rootContext.persistentStoreCoordinator = self.coordinator;

    self.uiContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    self.uiContext.parentContext = self.rootContext;

    self.syncContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.syncContext.parentContext = self.rootContext;
}

- (void)observeContextSaveNotification
{
//    [[NSNotificationCenter defaultCenter] addObserver:self
//                                             selector:@selector(onManagedObjectContextDidSaveNotification:)
//                                                 name:NSManagedObjectContextDidSaveNotification
//                                               object:nil];
}

- (void)onManagedObjectContextDidSaveNotification:(NSNotification *)notification
{
//    NSManagedObjectContext *context = notification.object;
//    if (context != self.uiContext) {
//        [self.uiContext mergeChangesFromContextDidSaveNotification:notification];
//    }
}

- (void)startTesting
{
    NSArray *personsBeforeSave = [self fetchEntities:@"Person" fromContext:self.uiContext];
    NSLog(@"Before save: %i persons in syncContext", [personsBeforeSave count]); // Before save: 0 persons in syncContext

    [self.syncContext performBlock:^{

        Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.syncContext];
        person.firstName = @"Alexander";

        NSError *error = nil;
        [self.syncContext save:&error];
        if (error) {
            NSLog(@"Error during save: %@", error);
        }

        NSArray *personsAfterSaveFromBackground = [self fetchEntities:@"Person" fromContext:self.rootContext];
        NSLog(@"After save from background: %i persons in rootContext", [personsAfterSaveFromBackground count]); // After save from background: 1 persons  in rootContext

        dispatch_async(dispatch_get_main_queue(), ^{
            NSArray *personsAfterSaveFromMain = [self fetchEntities:@"Person" fromContext:self.uiContext];
            NSLog(@"After save from main: %i persons in uiContext", [personsAfterSaveFromMain count]); // After save from main: 1 persons in uiContext
        });
    }];
}

- (NSArray *)fetchEntities:(NSString *)entity fromContext:(NSManagedObjectContext *)context
{
    NSError *error = nil;
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
    NSArray *result = [context executeFetchRequest:request error:&error];

    if (error) {
        NSLog(@"Error during fetch %@: %@", entity, error);
        return nil;
    }

    return result;
}

@end
Community
  • 1
  • 1
  • I am facing with the same phenomenon: http://stackoverflow.com/questions/30016148/strange-parent-child-nsmanagedobjectcontext-phenomenon – János May 29 '15 at 08:55
  • check out **How changes propagate** http://benedictcohen.co.uk/blog/archives/308 – János May 29 '15 at 08:57

1 Answers1

0

They are not being merged into the UI context. You are manually fetching them.

When you save in the syncContext, the data gets pushed up into the rootContext. The data is NOT merged into the uiContext. However, when you perform the fetch, the fetch pulls data down from the parent context.

You can get the objects in a context with registeredObjects.

Jody Hagins
  • 27,366
  • 6
  • 56
  • 85