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.