Peter Warbo
Peter Warbo

Reputation: 11710

Core Data NSManagedObject isFault inside prepareForDeletion

I'm doing some processing inside - prepareForDeletion in a NSManagedObject. However when I try to access some properties of this class they are returned to me as nil (even though I know they are not nil). So I check if my object is fault [self isFault] and it returns YES.

So how can I make the object "not fault" when I'm inside - prepareForDeletion because I need to access a property?

PhoneGroup.m (NSManagedObject)

- (void)prepareForDeletion {

    [super prepareForDeletion];

    NSArray *individuals = [self.individuals allObjects]; // individuals is nil

    for (int i = 0; i < [individuals count]; i++) {

        Individual *individual = [individuals objectAtIndex:i];

        BOOL individualExistsInOtherGroups = [individual.phoneGroups count] > 1;

        BOOL individualAddedAsAdditionalContactToReminder = [individual.reminders count] > 0;

        // We can delete the individual if individual does not exist in other groups and is not added to any reminders

        if (!individualExistsInOtherGroups && !individualAddedAsAdditionalContactToReminder && ![individual isDeleted]) {

            [[CoreDataHelper sharedInstance] deleteEntity:individual inManagedObjectContext:self.managedObjectContext];
        }
    }

    // Post a notification to notify all interested VC so they can refresh the GUI
    [[NSNotificationCenter defaultCenter] postNotificationName:RMContactDeletedNotification object:self];
    DLog(@"Contact deleted notification sent");
}

Model information:

PhoneGroup has an inverse to-many relationship to Individual with delete rule nullify.

Individual has an inverse to-many relationship to PhoneGroup with delete rule nullify.

Contact is a superclass for Individuals and PhoneGroups

Here's an image to visualize better.

enter image description here

EDIT: I have made some more findings on this issue

PhoneGroup only seems to fault when there is a relationship to a Reminder. If a PhoneGroup does not have a relationship to a Reminder when it is being deleted it is not fault.

Reminder has an optional inverse to-many relationship to Contact with delete rule nullify.

Contact has an optional inverse to-many relationship to Reminder with delete rule nullify.

What gives of this?

EDIT 2: More findins on the issue

I tried Dan Shelly's alternative solution to do a fetch of Individuals however even the self.managedObjectContext of the NSManagedObject (PhoneGroup) is nil inside - prepareForDeletion what does this imply?

Upvotes: 1

Views: 1847

Answers (3)

CMont
CMont

Reputation: 700

Late in the game, but I just had a few issues similar to that.

Is your store on iCloud?

My problems were related to the fact that I was using coredata with iCloud. Deleted objects coming from iCloud may already have their properties and related entities no more accessible, even before any call to mergeChangesFromContextDidSaveNotification.

I ended up not using any deleted object directly, nor relying on prepareForDeletion for accessing the data I needed.

Instead, I cache this information (in user settings is good for my use case), keyed with the objectID. Upon deletion, I retrieve that information using the objectID found in the NSDeletedObjectsKey of the notification user info. The cached info is deleted after that.

Upvotes: 0

Dan Shelly
Dan Shelly

Reputation: 6011

Why does [self.individuals allObjects] return nil?

Edit:
After discovering that your managedObjectContext is nil it is safe to say that your object has been disowned by its context.
This might be caused by a context reset or deallocation, while you maintain a strong reference to your object.
However this won't explain how this happen inside the prepareForDeletion method.
This method is invoked when a live context is deleting an object ([context deleteObject:...]).
This would mean that either you invoke prepareForDeletion manually ([object prepareForDeletion]), or that another thread is reseting/deallocating your context in mid-delete.

Note: The below code will work given that you have a valid objectID and a live managedObjectContext set on your object.

However, I might have a work around as the way you access your data is somewhat inefficient (will explain shortly):

If your deleted object (PhoneGroup) individuals relationship is not faulted, CoreData will forst access the store to fault the relationship (1st trip). you will then have faults as objects contained in that relationship (N objects), your loop will access them one by one (N trips to the store). still in the loop, you access each object for its relationships (2 relations ships, phoneGroups and reminders ==> 2 trips to the store PER ITEM).
In conclusion: 1 + N + 2*N = 3*N +1 trips to the store (worst case).

This could be accomplished in a much simpler way:
* create this fetch request:

NSFetchRequest* r = [[NSFetchRequest alloc] initWithEntityName:@"Individual"];
[r setPredicate:[NSPredicate predicateWithFormat:@"%@ IN phoneGroups AND phoneGroups.@count == 1 AND reminders.@count == 0",[self objectID]]];
[r setIncludesPropertyValues:YES];
[r setReturnsObjectsAsFaults:NO];
[r setIncludesPendingChanges:YES];

* now all that is left is:

NSManagedObjectContext* context = [self managedObjectContext];
NSArray* individuals = [context executeFetchRequest:r error:NULL];    
for (Individual* individual in individuals) {
    [context deleteObject:individual];
}

this is accomplished with a single (1) trip to the store in the worst case, and since you don't rely on you relationship directly, this should work as long as your object has its objectID.

Apendix:

Current implementation performance (SQLite debug):

//2013-05-19 21:34:42.700 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM Z_2PHONEGROUPS t1 JOIN ZCONTACT t0 ON t0.Z_PK = t1.Z_2INDIVIDUALS WHERE t1.Z_3PHONEGROUPS = ?
//2013-05-19 21:34:42.701 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.701 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 3 rows.
//2013-05-19 21:34:42.715 P[9666:c07] CoreData: annotation: to-many relationship fault "individuals" for objectID 0x8572b70 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/PhoneGroup/p21> fulfilled from database.  Got 3 rows
//2013-05-19 21:34:42.716 P[9666:c07] CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZCONTACTID, t0.ZVISIBLE FROM ZCONTACT t0 WHERE  t0.Z_PK = ?
//2013-05-19 21:34:42.717 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0005s
//2013-05-19 21:34:42.717 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 1 rows.
//2013-05-19 21:34:42.718 P[9666:c07] CoreData: annotation: fault fulfilled from database for : 0x818cad0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p4>
//2013-05-19 21:34:42.718 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM Z_2PHONEGROUPS t1 JOIN ZCONTACT t0 ON t0.Z_PK = t1.Z_3PHONEGROUPS WHERE t1.Z_2INDIVIDUALS = ?
//2013-05-19 21:34:42.719 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.719 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 1 rows.
//2013-05-19 21:34:42.719 P[9666:c07] CoreData: annotation: to-many relationship fault "phoneGroups" for objectID 0x818cad0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p4> fulfilled from database.  Got 1 rows
//2013-05-19 21:34:42.720 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM ZREMINDER t0 WHERE  t0.ZCONTACT = ?
//2013-05-19 21:34:42.721 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0005s
//2013-05-19 21:34:42.721 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 0 rows.
//2013-05-19 21:34:42.721 P[9666:c07] CoreData: annotation: to-many relationship fault "reminders" for objectID 0x818cad0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p4> fulfilled from database.  Got 0 rows
//2013-05-19 21:34:42.722 P[9666:c07] CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZCONTACTID, t0.ZVISIBLE FROM ZCONTACT t0 WHERE  t0.Z_PK = ?
//2013-05-19 21:34:42.722 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.722 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 1 rows.
//2013-05-19 21:34:42.723 P[9666:c07] CoreData: annotation: fault fulfilled from database for : 0x818cb90 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p16>
//2013-05-19 21:34:42.723 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM Z_2PHONEGROUPS t1 JOIN ZCONTACT t0 ON t0.Z_PK = t1.Z_3PHONEGROUPS WHERE t1.Z_2INDIVIDUALS = ?
//2013-05-19 21:34:42.724 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.724 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 1 rows.
//2013-05-19 21:34:42.724 P[9666:c07] CoreData: annotation: to-many relationship fault "phoneGroups" for objectID 0x818cb90 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p16> fulfilled from database.  Got 1 rows
//2013-05-19 21:34:42.725 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM ZREMINDER t0 WHERE  t0.ZCONTACT = ?
//2013-05-19 21:34:42.725 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.726 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 0 rows.
//2013-05-19 21:34:42.726 P[9666:c07] CoreData: annotation: to-many relationship fault "reminders" for objectID 0x818cb90 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p16> fulfilled from database.  Got 0 rows
//2013-05-19 21:34:42.727 P[9666:c07] CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZCONTACTID, t0.ZVISIBLE FROM ZCONTACT t0 WHERE  t0.Z_PK = ?
//2013-05-19 21:34:42.727 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.727 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 1 rows.
//2013-05-19 21:34:42.728 P[9666:c07] CoreData: annotation: fault fulfilled from database for : 0x818cba0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p17>
//2013-05-19 21:34:42.728 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM Z_2PHONEGROUPS t1 JOIN ZCONTACT t0 ON t0.Z_PK = t1.Z_3PHONEGROUPS WHERE t1.Z_2INDIVIDUALS = ?
//2013-05-19 21:34:42.729 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.729 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0008s for 4 rows.
//2013-05-19 21:34:42.729 P[9666:c07] CoreData: annotation: to-many relationship fault "phoneGroups" for objectID 0x818cba0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p17> fulfilled from database.  Got 4 rows
//2013-05-19 21:34:42.730 P[9666:c07] CoreData: sql: SELECT 0, t0.Z_PK FROM ZREMINDER t0 WHERE  t0.ZCONTACT = ?
//2013-05-19 21:34:42.730 P[9666:c07] CoreData: annotation: sql connection fetch time: 0.0004s
//2013-05-19 21:34:42.731 P[9666:c07] CoreData: annotation: total fetch execution time: 0.0007s for 0 rows.
//2013-05-19 21:34:42.731 P[9666:c07] CoreData: annotation: to-many relationship fault "reminders" for objectID 0x818cba0 <x-coredata://CBE585F2-552D-4FDE-AAEA-2C9C7984FAC9/Individual/p17> fulfilled from database.  Got 0 rows

Proposed implementation performance:

//2013-05-19 21:13:26.734 P[9609:c07] CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZCONTACTID, t0.ZVISIBLE FROM ZCONTACT t0 JOIN Z_2PHONEGROUPS t1 ON t0.Z_PK = t1.Z_2INDIVIDUALS WHERE ((t1.Z_3PHONEGROUPS = ? AND (SELECT COUNT(t3.Z_3PHONEGROUPS) FROM Z_2PHONEGROUPS t3 WHERE (t0.Z_PK = t3.Z_2INDIVIDUALS) ) = ? AND (SELECT COUNT(t4.Z_PK) FROM ZREMINDER t4 WHERE (t0.Z_PK = t4.ZCONTACT) ) = ?) AND  t0.Z_ENT = ?)
//2013-05-19 21:13:26.735 P[9609:c07] CoreData: annotation: sql connection fetch time: 0.0008s
//2013-05-19 21:13:26.736 P[9609:c07] CoreData: annotation: total fetch execution time: 0.0012s for 2 rows.

Data construction (- (void) synthesizePhoneGroupsAndIndividuals:(NSManagedObjectContext*)context):

NSMutableArray* individuals = [NSMutableArray new];//[NSEntityDescription insertNewObjectForEntityForName:@"Individual" inManagedObjectContext:context];
for (NSUInteger i = 0; i < (3 + arc4random() % 100); ++i) {
    Individual* indi = [NSEntityDescription insertNewObjectForEntityForName:@"Individual" inManagedObjectContext:context];
    indi.contactID = [NSString stringWithFormat:@"cid-%u",i];
    [individuals addObject:indi];
}

NSMutableArray* groups = [NSMutableArray new];
for (NSUInteger i = 0; i < 3 + arc4random() % 7; ++i) {
    PhoneGroup* group = [NSEntityDescription insertNewObjectForEntityForName:@"PhoneGroup" inManagedObjectContext:context];
    group.visible = NO;
    NSMutableSet* indis = [group mutableSetValueForKey:@"individuals"];
    for (NSUInteger j = 0; j < 3; ++j) {
        [indis addObject:[individuals objectAtIndex:(arc4random() % [individuals count])]];
    }
    [groups addObject:group];
}

[context save:NULL];

Testing code:

NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = _persistentStoreCoordinator;

[context performBlockAndWait:^{
    [self synthesizePhoneGroupsAndIndividuals:context];
    [context reset];
}];

[context performBlockAndWait:^{
    NSFetchRequest* r = [[NSFetchRequest alloc] initWithEntityName:@"PhoneGroup"];
    NSArray* groups = [context executeFetchRequest:r error:NULL];
    if ([groups count]) {
        PhoneGroup* group = [groups objectAtIndex:(arc4random() % [groups count])];
        [context deleteObject:group];
    }
}];

Upvotes: 1

idz
idz

Reputation: 12988

I suspect your problem may be caused by calling [super prepareForDeletion]; so early. You should probably do the work you want to do then call [super prepareForDeletion];.

Upvotes: 0

Related Questions