12

I'm one of the many getting the following Core Data error (133000):

Object's persistent store is not reachable from this NSManagedObjectContext's coordinator

As I've seen all over SO, this is because of Core Data's threading restrictions.
However, I've complied to all the correct threading restrictions, and yet this error is still coming through.

I create an NSManagedObject using the below code to perform the function on the main thread:

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                        selector:@selector(commitPlayer:)
                                                                          object:message];
[[NSOperationQueue mainQueue] addOperation:operation];
[operation release];

This function then creates the object and it's perfectly happy.
Then attempting to fetch the object with [objectContext existingObjectWithID:objectID error:&error] returns nil with the aforementioned error.

I've also wrapped the object fetching with an @synchonized mutex lock block just in case, and yet it is still failing to fetch that object.

It appears that saving the context before hand fixes this, however I want to fetch the object regardless as to whether it's saved or not as the DB shouldn't be saved at this point.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Tim
  • 597
  • 1
  • 5
  • 15

5 Answers5

15

Found the answer.
The problem was nothing to do with threading. It was simply because there was already a write happening (Hence why people assume via Occam's Razor that it's a threading issue).

The real reason this error shows, is not to do with threading, like the internet says, but because a read/write is already in progress at the same time you're attempting another read/write.

[objectContext existingObjectWithID:objectID error:&error] performs I/O on the database in order to guarantee the object that you get back actually does exist. However, in my case, I was already performing a read higher up in the stack.

It was attempting to read the parent's children, then attempting to read each child. Where, in fact, I should be using [objectContext objectWithID:objectID] to fetch a faulted object, and then perform the appropriate I/O once it's required.

This is not only the correct way of doing things, it also saves memory by not loading fifty billion child entities when you only wanted the list of them.

Hope that helps clarify things for someone!

Tim
  • 597
  • 1
  • 5
  • 15
  • Hi @Tim, Its not clear to me, can you please explain me , please have a look at this http://stackoverflow.com/questions/14646595/how-to-clear-reset-all-coredata-in-one-to-many-realationship – Ranjit Feb 04 '13 at 07:37
  • Ranjit, this error basically means that it can't write to the DB. This can be because of many reasons, but in a standard case, the most common situation this arises is a threading issue. But in that case, it's an error because a write is already happening, and you cannot open a new connection to the same DB while one is already active. Your issue is different, as you have removed the reference to the PSC, but it hasn't been removed or re-added correctly. – Tim Feb 05 '13 at 04:59
  • 2
    saving of same or similar objects crashing into one another appears to be a problem that i was having, so this is a good find and i'm glad it is out there along with the "different thread" reason.. which i don't have because i use the performSelectorOnMainThread method. – hokkuk Mar 13 '13 at 21:31
  • 1
    iCloud + coreData can cause this, bug in iCloud, even ios7 – J3RM Feb 10 '14 at 23:13
5

I want to share my case, which was related to NSManagedObjectContextDidSaveNotification:

I had this problem only in my UnitTests target, in the test cases which involved interaction with Core Data stack.

In my configuration of unit tests target each such case is preceded by beforeEach block having cleanUnitTestsDatabase() macros which performs a removal of Core Data's .sqlite3 file and reinstantiatiates a managed object context and persistent store coordinator to a new fresh state, so I have a whole database in a clean state each time I run in a new/next test case.

This caused the problem, because sometimes the following method was called after the current database had been axed because the current test case has been already passed and the next following test case has been entered already:

- (void)managedObjectContextDidSaveNotification:(NSNotification *)notification {
    NSManagedObjectContext *savedContext = [notification object];

    if ([savedContext isEqual:self.mainQueueManagedObjectContext] == NO) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        });
    }
}
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
  • 1
    Mmm, yes. If you were deleting the database on one thread while attempting to access it on another, it would be quite an issue. What would help this, is if you had two threads in parallel, with two test databases for each to access. Or you can simply keep it in series (all on one thread) so you can avoid database lock. Always need to watch out when doing threading. – Tim Sep 23 '13 at 20:45
2

I wanted to show an example of this problem of two reads of the NSManagedObject that will crash into each other and produce the error message in the Original question. but this will happen only if you have the NSManagedObjectContextDidSaveNotification set up, and are doing a mergeChangesFromContextDidSaveNotification... interestingly this notification was firing on a read of a valueForKey... not sure if there is a problem there.

    NSArray *theEvents = nil;
NSError *error = nil;

if ([currentObject valueForKey:@"event58Identifier"]) theEvents = [eventStore calendarItemsWithExternalIdentifier:[currentObject valueForKey:@"event58Identifier"]];

and how i solved it :0)

    NSArray *theEvents = nil;
NSError *error = nil;
NSString * identEvent = [currentObject valueForKey:@"event56Identifier"];

if (identEvent) theEvents = [eventStore calendarItemsWithExternalIdentifier:identEvent];

in any case, put in an NSLog and find as many saves as possible, because more than likely you don't need so many saves or reads, and try to get it all into one read or one write.

hokkuk
  • 675
  • 5
  • 21
  • This is a good example. The last paragraph is the exact solution to this issue in a generic way. Most sources claim it's simply a threading issue, but there are cases where this error can occur in the same thread, hence this exposure of this. – Tim Mar 13 '13 at 21:58
1

I was getting the same error, though my problem was slightly different. I had multiple NSManagedObjectContexts with different NSPersistentStoreCoordinators (a 3rd party library). They were having conflicts when the NSManagedObjectContextDidSaveNotification notification happens. That is, you're notifying one of the NSManagedObjectContexts of changes it doesn't know about. That's what "Object's persistent store is not reachable from this NSManagedObjectContext's coordinator" was saying with different words :)

HoBa
  • 3,442
  • 5
  • 26
  • 33
  • But to answer your question, no, I had one MOC, one PSC, but simply had a write happening from a callback from the MO. The callback is called before it closes the write operation, and thus the PSC denied my request to open a second write operation at the same time. – Tim Jan 24 '13 at 04:18
  • 1
    Makes sense. I was just having the same error and that was my issue. After staring to the same code for days (I'm new to IOS) and going through stackoverflow, I finally figured it out. Thought it was worth to mention on this thread since the error message is the same and might be useful to somebody else :) – HoBa Jan 24 '13 at 17:52
  • 1
    Then in that case, do you want to edit your answer to say it in the form of "This is what worked for me" rather than "Perhaps your problem is this?" Great to have you on iOS and putting up answers on StackOverflow though :D – Tim Jan 29 '13 at 03:50
1

I had the same error & it turns out it was because the temporary child MOC I was using to get object with ID did not have it's parent set to my main MOC.

All I ended up needing to do was this.

let tmpContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
tmpContext.parent = CoreDataStack.context

And then get objects with IDs if that's your goal.

Good luck :)

ethoooo
  • 536
  • 4
  • 12
  • 1
    This makes sense! If you've created a MOC without a parent, it believes it's a first-class context, and attempts to write on it's own schedule. With the parent connection, it waits it's turn and manages itself appropriately with the other context. Good job hunting that down! – Tim Jun 16 '20 at 07:00