Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some CoreData relationships are lost after closing app

This is the overview of my problem:

I am adding (and confirm they are added) about 1400 relationships loaded from a soap service into CoreDat. After I close the app and open it again some of the relationships are lost; I only see around 800 of them (although it varies). Also, I am not getting any errors.

And now, more details:

I have an object called User that has information about services a user have saved; it looks something like this:

@interface OosUser : NSManagedObject

    + (OosUser *) userFromSlug: (NSString *) slug;
    @property (nonatomic, retain) NSString *name;
    @property (nonatomic, retain) NSString *slug;
    @property (nonatomic, retain) NSMutableSet /* Service */ *services;
    - (void) addServicesObject: (Service * )service;
    - (void) removeServicesObject: (Service *) service;

@end

@implementation User

   @dynamic name;
   @dynamic slug;
   @dynamic services;

   static NSString *fetchPredicate = @"slug = %@";

   + (User *) userFromSlug:(NSString *)slug
   {
       User *result = [super objectWithPredicate: fetchPredicate, slug];
       if (!result) {
           result = [super create];
           result.slug = slug;
       }
   return result;
   }

@end

In the part of the code where the data is used, the relationships are saved like this:

NSMutableSet *userServices = self.user.services;

for (Service *service in servicesToAdd) {
        [self.services addObject: service];
        bool contained = false;
        for (Service *userService in userServices) {
            if ((contained = [userService.slug isEqualToString:service.slug])) {
                break;
            }
        }
        if (!contained) {
            // [userServices addObject:service];
            [self.user addServicesObject: service];
            NSError *error = nil;
            if (![[service managedObjectContext] save:&error]) {
                NSLog(@"Saving failed");
                NSLog(@"Error: %@", [error localizedDescription]);
            }else {
                NSLog(@"Registered service %d: %@", self.services.count, service.slug);
            }
        }
    }

The case is that I have checked with the debugger and I can see that all the over 1400 relationships are added, but when the app is reset and they are restored though self.user.services I only get around 800 objects.

Why could this be happening? Anybody had this before?

Thanks in advance.


UPDATE:

People keep suggesting that I am not using Core Data correctly but the problem is that the data is lost AFTER restarting the app. There is absolutely no problem with it while using it. I am using Core Data as correct as it could be given the limited documentation and examples you get from Apple.

like image 828
pablisco Avatar asked Dec 21 '11 09:12

pablisco


3 Answers

NSMutableSet *userServices = self.user.services;

...

            [userServices addObject:service];

Can't do that. self.user.services does not return a mutable set. That's what that addServicesObject method is for. Change the second line to:

            [self.user addServicesObject:service];

From Apple documentation:

It is important to understand the difference between the values returned by the dot accessor and by mutableSetValueForKey:. mutableSetValueForKey: returns a mutable proxy object. If you mutate its contents, it will emit the appropriate key-value observing (KVO) change notifications for the relationship. The dot accessor simply returns a set. If you manipulate the set as shown in this code fragment:

[aDepartment.employees addObject:newEmployee]; // do not do this! then KVO change notifications are not emitted and the inverse relationship is not updated correctly.

Recall that the dot simply invokes the accessor method, so for the same reasons:

[[aDepartment employees] addObject:newEmployee]; // do not do this, either!

like image 164
morningstar Avatar answered Oct 28 '22 16:10

morningstar


I've the same problem, but after set up the inverse relationship, everything is OK again. I hope this helps investigating this issue.

like image 3
dead_loop Avatar answered Oct 28 '22 16:10

dead_loop


Okay, let's look at the symptoms here. You are making changes to objects within a context, but when you restart the app, some but not all of these changes are there.

This can only mean that these changes are not being written to the persistent store. Since you are seeing some of the changes, you are calling the save: method, but Core Data is not recognising all the objects as needing to be saved.

This leads us to how you are performing the changes to said objects. Are you making the changes in a KVO-compliant manner? Core Data relies on KVO to know what changes have occurred, and therefore what changes need persisting. If you change the objects in a non-KVO fashion, they will appear to be changed when you examine them, but Core Data will not know that these changes exist, and will therefore not know that it must persist them.

I would amend your code as follows, providing the Service object has the inverse relationship back to the User (and that relationship is called user, and part of a 1-M):

[servicesToAdd setValue:self.user forKey:@"user"];

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

This relies on the fact that Core Data will take care of both ends of the relationship for you, where an inverse relationship is set up. Of course, if it's a many-to-many relationship, then the code is slightly different, but we still use the KVO-compliant methods to make the changes.

[servicesToAdd setValue:self.user forKey:@"user"];

for (Service *service in servicesToAdd) {
    [user addServicesObject:service];
} 

NSError *error = nil;
if (![[service managedObjectContext] save:&error]) {
    NSLog(@"Saving failed");
    NSLog(@"Error: %@", [error localizedDescription]);
}
like image 1
paulbailey Avatar answered Oct 28 '22 15:10

paulbailey