1

This is a test on part of Apple's Core Data PG, which I quote here

  • You started with a strong reference to a managed object from another object in your application.
  • You deleted the managed object through the managed object context.
  • You saved changes on the object context. At this point, the deleted object has been turned into a fault. It isn’t destroyed because doing so would violate the rules of memory management. Core Data will try to realize the faulted managed object but will fail to do so because the object has been deleted from the store. That is, there is no longer an object with the same global ID in the store.

So I setup a test project to see if it is the real case.

I'm using MagicalRecord to save some troubles creating MOCs, the code is based on a Core data model Class named "People"

@interface People : NSManagedObject
@property (nonatomic) int64_t userID;
@property (nullable, nonatomic, retain) NSString *name;

@end

In the test part, I wrap the MOCs MagicalRecord created into backgroundMOC and UIMOC so that those who are not familiar with MagicalRecord won't be confused.

UIMOC is BackgroundMOC's child and will merge backgroundMOC's changes by listening to NSManagedObjectContextDidSaveNotification backgroundMOC send out.

The "saveWithBlockAndWait" is just a wrapper around "performBlockAndWait". So here comes,

[[self backgroundMOC] MR_saveWithBlockAndWait:^(NSManagedObjectContext * _Nonnull localContext) {
    People *people = [People MR_createEntityInContext:localContext];
    people.userID = 1;
    people.name = @"Joey";
}];

People *peopleInMainThread = [People MR_findFirstInContext:[self UIMOC]];

NSLog(@"Before delete, name = %@", peopleInMainThread.name);

[[self backgroundMOC] MR_saveWithBlockAndWait:^(NSManagedObjectContext * _Nonnull localContext) {
    People *people = [People MR_findFirstInContext:localContext];
    NSLog(@"Deleting, name = %@", people.name);
    [localContext deleteObject:people];
}];

NSLog(@"After delete, name = %@", peopleInMainThread.name);

[[self UIMOC] save:nil];

NSLog(@"After save UIMOC, name = %@", peopleInMainThread.name);

The NSLog result is

Before delete, name = Joey     //As expected
Deleting, name = Joey          //As expected
After delete, name = Joey      //Shouldn't it be nil already? 
After save UIMOC, name = null  //As expected

This result seems to state that Merge from parent MOC won't make model objects fault, which could lead to some hard-to-find bugs or instead tedious checking codes everywhere. Again with the people object. I'll have to do things like this

- (void)codesInSeriousApp
{
[[self backgroundMOC] MR_saveWithBlockAndWait:^(NSManagedObjectContext * _Nonnull localContext) {
    People *people = [People MR_createEntityInContext:localContext];
    people.userID = 1;
    people.name = @"Joey";
}];

__block People *people = nil;
[[self UIMOC] performBlockAndWait:^{
    people = [People MR_findFirstInContext:[self UIMOC]];
}];

[self sendHttpRequestViaAFNetworking:^{
    //this block is executed on main thread, which is AFNetworking's default behavior
    if ([[self UIMOC] existingObjectWithID:people.objectID error:NULL])//[people isFault] would be NO here, and people's properties stay still.
    {
        //do something
    }
    else
    {
        //the people object is gone
        //maybe some codes on another thread deleted it and save to the backgroundMOC
        //the UIMOC merge the changes sent by notification, but the people object is still NOT FAULT!
    }
}];
}

As far as I can tell, for any model non-fault object in a specific MOC, say MOCA, the object won't be fault until [MOC save:&error] called all the way down to the persistent store. What really confuse me is, if Another MOC, already know that the object is fault by doing the saving chain, and MOCA merged changes that very MOC send out, how come the object in it is still non-fault? Am I misunderstood or anything? Any reply would be appreciated.

Thx in advance :)

NSFish
  • 354
  • 3
  • 10
  • It is as it should be. _You saved changes on the object context. At this point, the deleted object has been turned into a fault._. So, at the line `NSLog(@"After delete, name = %@", peopleInMainThread.name)` context `UIMOC ` is not yet saved. – bteapot Jun 01 '16 at 10:37
  • But before the "After delete" print, UIMOC did merged changes backgroundMOC send out, which is the deleted people object. So you're saying that merge won't make a model object fault? If so, this could be come some serious bugs. The UIMOC is the child of backgroundMOC, any changes in UIMOC could be push to backgroundMOC by calling save on UIMOC. But there're not methods that could push changes in backgroundMOC back into UIMOC except merge. So everytime I use model objects in mainThread, I have to check if its properties are not nil? – NSFish Jun 01 '16 at 10:39
  • Yes, changes from child context have been merged into `UIMOC`. It's an equivalent to calling `[[self UIMOC] deleteObject:peopleInMainThread]`. Since then, peopleInMainThread is marked for deletion. It will be faulted on `UIMOC`'s save. – bteapot Jun 01 '16 at 10:43
  • I've edit my comment, pls check again. BackgroundMOC is the parent of UIMOC, not the other way round. Calling save on parent MOC won't push changes to child MOC. – NSFish Jun 01 '16 at 10:45
  • Ah, pardon me! Look here: http://stackoverflow.com/a/17544420/826716 , _To bring changes from parent to the child, you need either merge changes using a notification method from parent's NSManagedObjectContextDidSaveNotification or re-fetch in child context_. – bteapot Jun 01 '16 at 10:49
  • And the situation stays the same: changes merged, object is marked for deletion. On `UIMOC`'s save it will be faulted. – bteapot Jun 01 '16 at 10:51
  • So, if you set some FRC on `UIMOC` to track changes in `People` entity's objects, it will notice that change and remove deleted object from its `fetchedObjects`, even before `UIMOC`'s save. If you are not relying on FRC and are using NSFetchRequest, you should refetch. – bteapot Jun 01 '16 at 10:55
  • Yes, that's what I have in mind. The point I'm trying to make here is, Since merge from parent MOC won't make deleted model objects fault, it could lead to some hard-to-find bugs. Say I have a People object in a serious app, and I rely on the object to send a http request. The people object is valid when I construct the request, but being deleted in backgroundMOC in another thread after sending the request. So when the http response comes back, I have to check the people object on UIMOC to see if it has become faulty? This is really tedious... – NSFish Jun 01 '16 at 11:04
  • The concurrency is tedious indeed! :) – bteapot Jun 01 '16 at 11:07
  • Here is a receipt for checking object's validity: http://stackoverflow.com/a/17970030/826716 – bteapot Jun 01 '16 at 11:11
  • I've update my question to describe more of my testing, would you mind check it again? It is kind of hard to explain my conclusion in comments.. – NSFish Jun 01 '16 at 11:33
  • `NSManagedObject` can be a 'fault', or it can be 'realized'. It's a normal Core Data object lifecycle, implemented to reduce memory footprint by filling object properties only when you need them. Object with any of that states are valid. Check for value of `NSManagedObject`'s `deleted` property. – bteapot Jun 01 '16 at 11:53
  • So yes, there could be a situation when you holding a reference to an object that has been deleted.That's a normal concurrency situation. You can subscribe for changes in MOC and look for particular object in `deletedObjects`. In case that it is in that list, you can cancel your request, etc. Here: http://stackoverflow.com/a/18424171/826716 – bteapot Jun 01 '16 at 12:01
  • This link is variable! I'm gonna dig deeper, thx for your Patience :) – NSFish Jun 01 '16 at 12:04

0 Answers0