Core data concurrency: avoid writing at two seperate places

361 views Asked by At

I am doing a little heavy core data insertion work (say A) just after login on a private context (to avoid main thread blockade). Before this gets completed, unfortunately at some place in code there is also an insertion work (say B) (not heavy) but is essential and also uses private context to save.

ISSUE: while saving the records anywhere i check whether they are already in DB or not (to ensure unique records), But after the above tasks are finished and i check coredata i find some records duplicated, which simply is due to that before I call 'save' at task A , the other process B had already written a duplicate record.

I am looking for a settlement, foreseeign two options:

  1. let second task wait for first task (dispatching in another thread and wait untill completed)
  2. plz recommend!!

EDIT: its turning out that the issue is more related to merging rather than concurrency, the merging creates duplicate records, concurrency is just fine, the error thrown is:

NSCocoaErrorDomain Code=133020

UPDATE: Merge issue was fixed with overwrite merge policy

1

There are 1 answers

1
Daniel Galasko On BEST ANSWER

You said it yourself in the question, your objects are being duplicated because you are saving from two distinct locations.

One strategy is to perform all your writes using the same NSManagedObjectContext. There is rarely a need to create multiple background context's to perform writes (Unless perhaps your writes are taking a long time).

That being said clearly you happen to be in a position where your two Write operations can possibly write the same object. I would manage all writes through a single interface, then this interface will retain the private context for writing. Since a context is a serial queue you can guarantee that you won't be able to write the same object twice (Assuming your checks for duplication are rock solid).

So your interface might look like this:

@interface CoreDataManager : NSObject
+ (CoreDataManager*)sharedManager;
- (void)performWriteWithBlock:(void (^)(NSManagedObjectContext *writeContext))writeBlock;
@end

@implementation CoreDataManager
+ (CoreDataManager *)sharedManager {
    static dispatch_once_t once;
    dispatch_once(&once, ^ {
        self.writeContext = [[self class] createBackgroundPersistanceObjectContext];
    });
    return sharedManager;
}

- (void)performWriteWithBlock:(void (^)(NSManagedObjectContext *writeContext))writeBlock {
    if (writeBlock) {
        writeBlock(self.writeContext);
        [self.writeContext save:nil];
    }
}

+ (NSManagedObjectContext *)createBackgroundPersistanceObjectContext {
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [context setPersistentStoreCoordinator:coordinator];
    context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
    context.undoManager = nil;
    return context;
}
@end

Nevertheless there are many ways you could define an interface but what is important is that you have a single interface dealing with your Core Data reads and writes.