samedi 25 juin 2016

managed object property set to nil during save


I have a managed object named SpecialItem and call setSubcategory to change the subcategory. When I save the temporary context and merge with the main context, somehow setSubcategory is called passing in nil as the subcategory. This often results in the SpecialItem object being saved with myProperty set to nil. I don't know what is calling setSubcategory. I'm not explicitly calling setSubcategory:nil.

My question is, what is happening and how can I fix this?

Here is the log:

-[DataCollectionController saveContext] [Line 2776] Before save.
-[SpecialItem setSubcategory:] [Line 23] ---- self.myProperty is Test Multiple 1 (10004) but is becoming nil! objectID: 0x7fd7095fb500 <x-coredata:///MyProperty/t1DCE6D07-437B-40E5-A8E4-789D1A2DC9074>
-[SpecialItem setSubcategory:] [Line 34] Changes complete.
-[DataCollectionController saveContext] [Line 2786] Middle of save.
-[SpecialItem setSubcategory:] [Line 23] ---- self.myProperty is Test Multiple 1 (10004) but is becoming nil! objectID: 0xd00000027120000e <x-coredata://6E3B96F2-F3A9-4B75-A9F0-FFDC5FC854B3/MyProperty/p40008>
-[SpecialItem setSubcategory:] [Line 34] Changes complete.
-[DataManager synchronize] [Line 401] Synchronizing Core Data changes.

This is the managed object implementation:

@interface SpecialItem : GenericItem
@property (nonatomic, strong) Subcategory *subcategory;
@property (nonatomic, strong) MyProperty *myProperty;
@end

@implementation SpecialItem
@dynamic subcategory;
@dynamic myProperty;

- (void)setSubcategory:(Subcategory *)subcategory
{
   BOOL didTrigger = NO;
   if(subcategory == nil && self.myProperty != nil) {
      didTrigger = YES;
      LogAndPrint(@"---- self.myProperty is %@ (%@) but is becoming nil! objectID: %@", self.myProperty.name, self.myProperty.id, self.objectID);
   }

   [self willChangeValueForKey:@"subcategory"];
   [self willChangeValueForKey:@"myProperty"];

   [self setPrimitiveValue:subcategory forKey:@"subcategory"];
   [self setPrimitiveValue:subcategory.category.myProperty forKey:@"myProperty"];

   [self didChangeValueForKey:@"myProperty"];
   [self didChangeValueForKey:@"subcategory"];

   if(didTrigger)
      LogAndPrint(@"Changes complete.");
}
// ...

The managed object contexts are setup like so:

self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.tempContext.parentContext = self.dataManager.mainContext;

Somewhere in code I have this:

Subcategory *existingSubcategory = (Subcategory *)[self.tempContext objectWithID:subcategory.objectID];
self.tempSpecialItem.subcategory = existingSubcategory;

That code is called multiple times during use and often times on the same object, which is fetched from the tempContext.

Eventually I have this:

[self saveTempContext];

Here is the saveContext implementation:

- (void)saveContext
{
   LogAndPrint(@"Before save.");
   [self.tempContext performBlockAndWait:^{
      NSError *error = nil;
      if (![self.tempContext save:&error])
      {
         LogAndPrint(@"Error occurred while saving context: %@", error);
      }
   }];

   LogAndPrint(@"Middle of save.");

   [self.dataManager.mainContext performBlockAndWait:^{
      NSError *error = nil;
      if (![self.dataManager.mainContext save:&error])
      {
         LogAndPrint(@"Error occurred while saving context: %@", error);
      }
   }];

   [self.dataManager synchronize];
   LogAndPrint(@"After save.");
}

Here is the synchronize implementation:

- (void)synchronize
{
   LogAndPrint(@"Synchronizing Core Data changes.");
   if (self.persistentContext.hasChanges) {
      [self.persistentContext performBlockAndWait:^{
         NSError *error =  nil;
         if (![self.persistentContext save:&error]) {
            LogAndPrint(@"Error occurred while saving persistent context: %@", error);
         }
      }];
   }
}

Aucun commentaire:

Enregistrer un commentaire