0

I have a second MOC in a thread that executes a fetch request and updates 4 properties. In the main thread i have NSManagedObjectDidSaveNotification handler registred that merges the changes from my second MOC. if i put a break point in this method and check the updated objects, then i do see new properties. However, if i add object id's in an arrary and post merge+save access the same objects with the help of their ID's then i get old properties.

Surprizingly, i have observed that out of 2, one property has new value while the other one does not. This is mind boggling and after several hours of debugging, i am unable to understand the reason behind this. it will be helpful if geniuses threw some light on this issue.

Update

In my main thread:

[[NSNotificationCenter defaultCenter] addObserver:self
                                        selector:@selector(contextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:nil];


- (NSManagedObjectContext *)mainThreadObjectContext 
{

    if (myMainThreadObjectContext == nil) {
       NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        myMainThreadObjectContext = [[NSManagedObjectContext alloc] init];
        [myStateManagedObjectContext setPersistentStoreCoordinator:coordinator];
        [myStateManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
        [myStateManagedObjectContext setRetainsRegisteredObjects:NO];
        [myStateManagedObjectContext setUndoManager:nil];
    }
}
return myMainThreadObjectContext;

}

I merge the changes done in threaded MOC in this method

- (void) contextDidSave:(NSNotification *)notification
  {
if (![NSThread isMainThread])
{
    [self performSelectorOnMainThread:@selector(contextDidSave:) withObject:notification waitUntilDone:NO];
    return;
}
NSManagedObjectContext *moc = [notification object];
NSMutableSet *updatedObjects = [NSMutableSet set];

if (![moc isEqual:self.mainThreadManagedObjectContext] && [[moc persistentStoreCoordinator] isEqual:self.persistentStoreCoordinator])
{
    @synchronized(self)
    {

            NSDictionary *userinfoDict = [notification userInfo];
            NSSet *insertedObjectSet = [userinfoDict valueForKeyPath:@"inserted.objectID"];
            NSSet *updatedObjectSet =  [userinfoDict valueForKeyPath:@"updated.objectID"];
            [updatedObjects addObjectsFromArray:insertedObjectSet.allObjects];
            [updatedObjects addObjectsFromArray:updatedObjectSet.allObjects];



        @try {
            [self.mainThreadManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }
        @catch (NSException * e) {
            NSLog(@"Exception %@ when mergning contexts in -[%@ %@]", e, NSStringFromClass(self.class), NSStringFromSelector(_cmd));
        }


        if(updatedObjects.count>0)
        {
            [self performSelector:@selector(updatedStatus:) withObject:updatedObjects afterDelay:0.5];
        }

        NSError *saveError = nil;
        if (![self.mainThreadManagedObjectContext save:&saveError])
        {
            NSLog(@"Could not save objects in -[%@ %@]. Error = %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), saveError);
        }

       }


  }
  }

In the main thread i have methods that fetch existing objects from MOC and update properties that are received from a server. However, In my secondary thread, i have methods that insert new managed objects and later update properties that take a long time to process in the main thread

In the second thread, i have following code

-(void) startTaskThread
{
    thread_ = [[NSThread alloc] initWithTarget:self selector:@selector(executeTask)     
    object:nil];
    [thread_ start];
}


-(void) executeTask
{
  @synchronized(self)
  {
    [self saveContext];

     NSLog(@"\n\nStarting Task thread [%@]!!!!\n\n", self);

    for(;;)
    {
        if([[NSThread currentThread] isCancelled])
            break;

        @autoreleasepool
        {
            [condition_ lock];

            if(taskArray_.count==0)
            {
                [condition_ wait];
            }

            [condition_ unlock];

            @try {
                if(taskArray_.count>0)
                {
                    NSInvocation *invocation = [self.taskArray objectAtIndex:0];


                    if([[NSThread currentThread] isCancelled])
                        break;
                    else if(!([[NSThread currentThread] isCancelled]) && [self     
                 respondsToSelector:invocation.selector ])
                    {

                        [invocation invokeWithTarget:self];
                        [self.taskArray removeObjectAtIndex:0];
                    }

                }
            }
            @catch (NSException *exception) {
                NSLog(@"Exception %@", exception.debugDescription);
            }
            @finally {

            }
        }
    }

  }
    // Save Moc.
    if([[[self managedObjectContext] insertedObjects] count]>0 || ([[[self 
             managedObjectContext] updatedObjects] count]>0) )
    {

        NSError *error = nil;
        if(![[self managedObjectContext] save:&error])
        {
            NSLog(@"Moc Save Error %@", [error localizedDescription]);
        }

    }

     [self.managedObjectContext reset];

}  

The saveContext method in my secondary thread is as follows:

-(void) saveContext
{

if(!thread_ || [thread_ isCancelled])
{
    // Clean up cache here  //
    return;
}


_/* --- Here i have code that iterates through the properties that were received from the network and stored in a cache of NSDictionary data type */_ 


// Now i save the threaded MOC.

if([[[self managedObjectContext] insertedObjects] count]>0 || ([[[self managedObjectContext] updatedObjects] count]>0) )
{

    NSError *error = nil;
    NSLog(@"We are merging the changes now.. [%d], [%d]", [[[self managedObjectContext] insertedObjects] count], [[[self managedObjectContext] updatedObjects] count]);
    if(![[self managedObjectContext] save:&error])
    {
        NSLog(@"Moc Save Error %@", [error localizedDescription]);
    }

}

    _// I create a dispatch queue here to repeteadly call this method. This serves as a hear-beat dispatch queue that checks for updated/inserted objects every 5 seconds._

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_queue_t queue = dispatch_queue_create("com.myCompany.mocSaveQ", 0);
    dispatch_after(popTime, queue, ^(void)
    {
                       // Save Moc.
                       NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(saveContext)]];
                       invocation.selector = @selector(saveContext);
                       invocation.target = self;

                       [self addTask:invocation];

    });


    dispatch_release(queue);

}

Now, in the debugger, i tried to print updated objects in the thread before calling [NSManagedObjectContext save] on the threaded/second MOC and i do see the changes applied to the respective properties of that Managed Object. However, when i try to print the notification objects' userInfo dictionary for updated objects in contextDidSave: method in my main thread, though it lists the same objects only one property that was updated in the thread does not have new value. This is pretty confusing to me.

Update 2

Here is more code to help understand it better.

In my main thread

The data dictionary is an NSDictionary that contains properties as well as sub dictionaries

-(void) processData:(NSDictionary*) data
 {
      for(NSString *key in data)
      {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        fetchRequest.entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:self.managedObjectContext];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"lookupKey = %@", key];
    NSArray *fetchResults = nil;
    NSError *error = nil;

    @synchronized(@"fetch_request_for_lookupKey")
    {
        @try
        {
            fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
        }
        @catch (NSException *exception)
        {
            NSLog(@"Exception %@ when executing fetch %@", exception, NSStringFromSelector(_cmd));
        }
        @finally
        {
            [fetchRequest release];
        }
    }


            NSManagedObject* myManagedObject = fetchResults.lastObject;
            // We get the dictionary here
            NSDictionary *dictionary = [taskDictionary objectForKey:lookUpKey];

            for(NSString *subKey in dictionary)
            {

                 if([key isEqualToString:@"name"])
                 {
                        myManagedObject.name = [dictionary objectForKey:subKey];
                 }
                 else if([key isEqualToString:@"phone"]
                 {   //....Update the value...//      }


            }
      }
 }

Now in my secondary threaded class, i have the data processing logic listed above in saveContext method in my secondary thread. If you see above, then i save my Main thread MOC and only then execute NSFetchRequest in my thread. I also made sure that there are no updated objects in the Main MOC after executing the save and before executing NSFetchRequest in my secondary thread.

user598789
  • 329
  • 1
  • 2
  • 17
  • Good luck getting Apple Engineers to answer your question, mate. Unless @davedelong is willing to give you some of his time ;) – CodaFi Jan 08 '13 at 23:47
  • Perhaps you could upload a sample project/gist clearly illustrating the problem. – Alex Jan 09 '13 at 00:11
  • lol..sorry about mentioning Apple engineers here. Actually i copy+pasted my own question from devforums.apple.com. I had posted it yesterday and haven't received any answers. Hence, i decided to widen my audience by positing it here :). The code is pretty simple actually. But i will post few snippets to get an idea. – user598789 Jan 09 '13 at 00:26
  • 1
    @user598789 code snippets are *always* helpful. – Dave DeLong Jan 09 '13 at 02:30
  • There's no obvious reason for this issue from your description, show some code. – Tom Harrington Jan 09 '13 at 03:24
  • Perhaps you have the problem described here: http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different. `mergeChangesFromContextDidSaveNotification` does not update objects which are a fault in the current context. Perhaps the workaround from the answer to that question helps: Call `willAccessValueForKey:nil` for all updated objects to force the current context to fire a fault (before calling `mergeChangesFromContextDidSaveNotification`). – Martin R Jan 09 '13 at 07:50
  • Issue here is that the objects changed in the thread show properties correctly while the userInfo for the notification object received in contextDidSave in the main thread shows same objects that were updated in the thread but have different property value. I am not fetching any objects between a save (in secondary thread) and merge in main thread. So my concern is that if i updated a property to some new value in my thread and i save the second MOC then i am expecting the same information to be transmitted into NSNotification objects' userInfo and unfortunately that is not the case. – user598789 Jan 09 '13 at 18:07
  • Interestingly, the issue is solved after i reset the threaded/second MOC after i save it. This makes me wonder why that is the case? – user598789 Jan 09 '13 at 23:03
  • I also noticed that any objects updated in threaded/second MOC will be marked as updated forever. As listed above, i am saving my context periodically and i could see the same objects being returned from updatedObjects method even after several saves. – user598789 Jan 09 '13 at 23:05
  • @davedelong can you please comment? – user598789 Jan 09 '13 at 23:51

1 Answers1

0

That's what I found on the documentation:

Concurrency

Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see “Concurrency with Core Data”). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread. Instead, you should pass a reference to a persistent store coordinator and have the receiving thread/queue create a new context derived from that. If you use NSOperation, you must create the context in main (for a serial queue) or start (for a concurrent queue).

In OS X v10.7 and later and iOS v5.0 and later, when you create a context you can specify the concurrency pattern with which you will use it (see initWithConcurrencyType:).

So as it states I'd suggest to use this method to get the MOC object:

- (id)initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct;
Community
  • 1
  • 1
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187