title: Core Data duplicates when entries are updated but not saved
body: <p>I'm trying to complete the <a href="http://orion.lcg.ufrj.br/iphone/stanford/ios6/slides/Assignment%206_%20Core%20Data%20SPoT.pdf" rel="nofollow">Assignment 6 from course CS193P of Paul Hegarty</a>. In a word, this is an iOS application used to browse photos downloaded from Flickr. The application has two tabs:</p>
  
  <ul>
  <li>The first tab is used to browse photos by tag. When you click a tag, this lists the photos with this tag set. When you click the photo, it is displayed. All this is achieved through Table Views embedded in a Navigation Controller, and finally a UIScrollView for the display.</li>
  <li>The second tab is used to browse recently viewed photos through a Table View.</li>
  </ul>
  
  <p>Photo and tag information is stored in Core Data. This data is displayed in tables through a <code>NSFetchedResultsController</code>.</p>
  
  <p>Here is my issue: as long as I do not update the Core Data objects, everything is fine. When I update an object (for example, setting the lastViewed property on a photo, so that I can display it in the Recent tab), the corresponding photo is downloaded again from Flickr on next Table View Refresh, which leads to a duplicate entry in the Table View. <strong>After a long debugging session, I finally discovered the issue, but I cannot explain why: this is due to an update on a Core Data object without explicitly saving the change.</strong></p>
  
  <p>I've read the <a href="http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html#//apple_ref/doc/uid/TP40001075" rel="nofollow">Core Data Programming Guide</a>, as well as the different Class Reference documentations, but I didn't manage to find an answer regarding this.</p>
  
  <p>Here is the code to update the Photo lastViewed property when the user wants to display it. If I uncomment the line <code>[[SharedDocument sharedInstance] saveDocument]</code>, everything works as expected. If I comment it, the viewed photo will be downloaded again on next refresh, whereas it already exists in Core Data.</p>
  
  <pre><code>- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  {
      NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
  
      // - Variable check discarded for a readability purpose -
  
      if ([segue.identifier isEqualToString:@"setImageURL:"])
      {
          Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];
  
          // Update the last viewed date property to now
          photo.lastViewed = [NSDate dateWithTimeIntervalSinceNow:0];
  
          // If I uncomment the line below, the issue disappears:
          // [[SharedDocument sharedInstance] saveDocument];
  
          if ([segue.destinationViewController respondsToSelector:@selector(setImageURL:)])
          {
              // Prepare the next VC
          }
      }
  }
  </code></pre>
  
  <p>The <code>NSManagedObjectContext</code> is shared. Here is the code from the shared object:</p>
  
  <pre><code>@interface SharedDocument()
  @property (strong, nonatomic)  UIManagedDocument * document;
  @end
  
  @implementation SharedDocument
  
  + (SharedDocument *) sharedInstance
  {...} // Returns the shared instance
  
  - (UIManagedDocument *) document
  {...} // Lazy instantiation
  
  - (void) useDocumentWithBlock:(void (^)(BOOL success))completionHandler
  {...} // Create or open the document
  
  - (void) saveDocument
  {
      [self.document saveToURL:self.document.fileURL
              forSaveOperation:UIDocumentSaveForOverwriting
             completionHandler:nil];
  }
  </code></pre>
  
  <p><strong>Updates:</strong></p>
  
  <p>Note about Flickr photos: a set 50 photos is available for download from Flickr. This is a finite set, i.e. no new photos will be added nor updated. So, when I refresh a Table View, no new Photo should be downloaded.</p>
  
  <p>A Photo object is created this way (this is a Category from a <code>NSManagedObject</code> subclass):</p>
  
  <pre><code>+ (Photo *) photoWithFlickrInfo:(NSDictionary *)photoDictionary
           inManagedObjectContext:(NSManagedObjectContext *)context
  {    
      Photo * photo = nil;
  
      // Check whether the photo already exists
      NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
      NSString *pred = [NSString stringWithFormat:@"uniqueId = %@",
                        [photoDictionary[FLICKR_PHOTO_ID] description]];
      request.predicate = [NSPredicate predicateWithFormat:pred];
      NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"uniqueId"
                                                                       ascending:YES];
      request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
  
      NSError *error = nil;
  
      NSArray *matches = [context executeFetchRequest:request error:&amp;error];
  
      if (!matches || ([matches count] &gt; 1) || error)
      {
          // Abnormal
          NSLog(@"Error accessing database: %@ (%d matches)", [error description], [matches count]);
      }
      else if (0 == [matches count])
      {
          // Create the photo
          photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                                inManagedObjectContext:context];
  
          photo.uniqueId = photoDictionary[FLICKR_PHOTO_ID];
          photo.title    = [photoDictionary[FLICKR_PHOTO_TITLE] description];
          photo.comment  = [[photoDictionary valueForKeyPath:FLICKR_PHOTO_DESCRIPTION] description];
  
          if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
          {
              photo.imageURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                    format:FlickrPhotoFormatOriginal] absoluteString];
          }
          else
          {
              // iPhone
              photo.imageURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                    format:FlickrPhotoFormatLarge] absoluteString];
  
          }
  
          photo.thumbnailURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                    format:FlickrPhotoFormatSquare] absoluteString];
  
          photo.section = [photo.title substringToIndex:1];
  
          // Update the category / tag
          for (NSString * category in [photoDictionary[FLICKR_TAGS] componentsSeparatedByString:@" "])
          {            
              // Ignore a couple of categories
              if ([@[@"cs193pspot", @"portrait", @"landscape"] containsObject:[category lowercaseString]])
                  continue;
  
              Tag *tag = [Tag withName:[category capitalizedString] forPhoto:photo inManagedObjectContext:context];
  
              [photo addTagsObject:tag];
          }
  
          NSArray *allTags = [[photo.tags allObjects] sortedArrayUsingComparator:^NSComparisonResult(Tag * t1, Tag * t2) {
              return [t1.name compare:t2.name];
          }];
  
          photo.tagString = ((Tag *) [allTags objectAtIndex:0]).name;
          NSLog(@"PhotoTagString: %@", photo.tagString);
  
          // Update the specific 'All' tag
          Tag * allTag = [Tag withName:@"All" forPhoto:photo inManagedObjectContext:context];
          [photo addTagsObject:allTag];
  
          NSLog(@"[CORE DATA] Photo created: %@ with %d tags", photo.uniqueId, [photo.tags count]);
      }
      else
      {
          // Only one entry
          photo = [matches lastObject];
  
          NSLog(@"[CORE DATA] Photo accessed: %@", photo.uniqueId);
      }
  
      return photo;
  }
  </code></pre>
  
  <p>I hope my explanation was clear enough. Tell me if you need more information to understand the issue (this is my first post, I'm still a young padawan :-)</p>
  
  <p>Thanks a lot in advance,</p>
  
  <p>Florian</p>