Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSManagedObject stays fault

Tags:

ios

core-data

My situation: Assuming I have a class Person(subclass of NSManagedObject). Everytime the user clicks on a button a new Person instance will be created and added to a global NSMutableArray. Also the new created Person instance will be added to child context, like this way:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateConcurrencyType];
[childContext setParentContext: _mainContext];

Also when clicking the button I save the context: (it is a little bit more complicated, but follows this structure)

[childContext performBlock:^{
    [childContext save:nil];
    [_mainContext save:nil];
}];

After clicking two or more times (not sure if it depends on the total clicks) on the button objects in my array becomes fault.

According to the docs: Accessing a property of a fault object should load the persistent object.
Even when I am accessing a property of my NSManagedObject the object is still fault and property is nil.

Why are objects fault in my array and how do I access a property of a fault object?

Edit:

When loading the UIViewController I'm fetching all existing objects from the datastore:

-(NSArray*)fetchPersons {
    NSManagedObjectContext *context = [self managedObjectContext]; //this is _mainContext, it is created with initWithConcurrencyType:NSMainQueueConcurrencyType
    NSFetchRequest  *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *description = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
    [fetchRequest setEntity:description];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"key = %i", aCondition];
    [fetchRequest setPredicate:predicate];
    return [context executeFetchRequest:fetchRequest error:nil];
}

I'm using this NSArray from fetchPersons to populate the NSMutableArray.

Creating a new Person object:

-(Person*)createPerson {
    NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; 
    [childContext setParentContext:[self managedObjectContext]];
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:childContext];
    return person;
} 

I'm not sure how to handle the objects with the òbjectID`. I'm using the childContext as a temporary context. Sometimes I need a Person instance, but I don't want to save it to the persistent store (or insert it to the main context at the beginning).

After all these steps I have all objects in my NSMutableArray. I get nil properties (fault object) after creating some objects and try to log their attributes (person.name or something).

like image 297
Sebastian Avatar asked Feb 06 '13 21:02

Sebastian


1 Answers

NSManagedObjects retain a tight relationship with the NSManagedObjectContext on which they were created. The specific reasons for this are that managed objects are always mutable, so may need to write back to the store, and there's an expectation of future faulting — Core Data explicitly may choose not to load the whole persistent store into memory but may have to deal with your future arbitrary traversal of the object graph or with low memory warnings.

(aside: this is also the most proximate reason why Core Data objects can't be used on anything other than the thread/queue that they were created on; it's the contexts and the various bits of implicit communication between contexts and objects that aren't safe)

What that means in practice is that you should't allow a managed object to outlive its store.

To allow you to send objects back and forth between different agents with different contexts, Apple implements the NSManagedObjectID which is a unique identifier for any managed object. It's a fully opaque class but, for information, if you have a SQLite store then it's a reference to the relevant table and row; if you have one of the other store types then it's similarly a pointer to a location in the store. It carries none of the object data itself.

So what you'd normally do is call objectID on an object while the context it was created under is still alive. You can then pass that over to whoever else wants it. They'll then use [myManagedObjectContext -existingObjectWithID:error:] to get a new copy of the managed object that they can use safely. The new copy will be bound to that context rather than to the original so will be safe whenever and for however long that context is safe, rather than the original.

The only potential surprise is that when you first insert an object, it gets only a temporary object ID. That's because Core Data likes to batch up things that will need inserting into the store and then insert them all at once only when you request a save.

For your purposes you don't want to pass the ID up until after a save anyway because the object won't exist in the parent store. So the point is partly academic but supposing you wanted to ferret away the IDs for some other reason, you might also consider using the context's -obtainPermanentIDsForObjects:error:, which can be a lot faster than the actual save, depending on your store type, and will definitely never be slower.

like image 192
Tommy Avatar answered Oct 16 '22 13:10

Tommy