EXC_BAD_ACCESS when trying to access a managed object property - objective-c

I'm having some trouble dealing with Core Data+concurrency/nested MOCs (not sure which one I'm having problems with =P).
I have a method where I pass in a managed object ID (I checked that it's permanent) and that method has a child managed object context that is confined to a certain queue. I can retrieve the object from the child managed object context via [managedObjectContext objectWithID:moID] but when I try to access any of its properties (the managed object is still a fault), I get EXC_BAD_ACCESS with the stack trace showing _svfk_1 and objc_msgSend.
I know it's kind of difficult to figure out what the problem is without sample code, but I was hoping someone could shed some light on the possible causes. Thanks. =)
EDIT: I tried using existingObjectWithID:error: instead of objectWithID: as Tom Harrington suggested and now it works sometimes but doesn't work other times. I also experienced an EXC_BAD_ACCESS crash on mergeChangesFromContextDidSaveNotification:. I suspect this could be a synchronization issue. If I edit something in one context and save while something else is edited in my child context, would that cause an issue?
EDIT 2: I figured out why existingObjectWithID:error: was working sometimes but not always. The managed object ID was indeed a temporary ID (shouldn't mergeChangesFromContextDidSaveNotification: convert it to a permanent ID?), so I had to call obtainPermanentIDsForObjects:error: before passing the ID. But I'm still getting crashes sometimes in the child context's mergeChangesFromContextDidSaveNotification:. What could be the possible causes of this? Thanks.
EDIT 3: Here's what my MOC hierarchy looks like.
Persistent Store Coordinator
|
Persistent Store MOC
/ \
Main Queue MOC Child MOC (confinement)
I'm invoking a method from the main queue that uses the Child MOC (in another queue) to insert and update some managed objects and at the same time, I'm inserting and updating managed objects in the Persistent Store MOC. Managed objects can also be updated, deleted, and inserted in the Main Queue MOC at the same time. I merge any changes from the Persistent Store Coordinator to both the Main Queue MOC and the Child MOC.
Some more questions: Does saving an MOC automatically merge things? If there is a pending merge request for an MOC and you save before that merge request is processed, can that cause issues?
EDIT 4: Here's how I initialize the Child MOC.
dispatch_sync(_searchQueue, ^{
_searchManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[_searchManagedObjectContext setParentContext:_persistentStoreManagedObjectContext];
[_searchManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
});
Btw, I notice that the merge only crashes (with EXC_BAD_ACCESS) when the notification contains deleted objects.

It looks like you're still working too hard. For your child MOC, since it's in a serial queue, use NSPrivateQueueConcurrencyType, and set its parent to your main MOC.
NSConfinementConcurrencyType is for legacy configurations.

I found a fix. Before every save, I do [moc obtainPermanentIDsForObjects:[[moc insertedObjects] allObjects] error:&error]. Now I don't get any more crashes.
I'm still a bit murky on what exactly was going on, but here's my understanding. When you save newly inserted objects, they are only assigned a permanent ID when the MOC connected to the persistent store coordinator saves. Now, either mergeChangesFromContextDidSaveNotification: propagated the permanent IDs back down (as I had expected) and some other operation just happened to occur just before the merge or there's an Apple bug somewhere. In any case, obtaining the permanent IDs beforehand solved the issue.
TL;DR Core Data+concurrency is difficult.

Related

CoreData: how to leave particular objects in context unsaved and save others?

Prerequisites:
I have 2 methods in the network API:
return list of entities (just basic info: name, id, etc.)
return detailed info about entity
The requirement is to save only objects created by processing the second request (save to sqlite) and leave non-full objects without saving.
Also, the 'main' context should contain only full objects from 1st request, and any other 'temporary' context should contain all the others objects.
I've tried to create two instances of NSPersistentStoreCoordinator and use them for different types of contexts, but it seems that for one NSManagedObjectModel can exist only one coordinator (the pointer points to the same adress).
If I understand you correctly, then I think your best option is to only create a managed object once you're sure you want it to persist in Core Data storage. You may need another layer of non-managed objects to contain data for the "non-full" entities
(This would be something like Data Transfer Objects from Java EE programming.)
You can not save indiscriminately from within the same MOC. Saving the MOC always saves everything in it.
So, you can use a separate MOC that is never saved, and then just "move" those objects to the main MOC when they are ready to be saved.
The solution that exactly solves my problem is to create two 'forks' of core data stack:
one with default config and mainContext
the second (new instance of the same NSManagedObjectModel, new
NSPersistentStore (inMemory) and new NSPersistenStoreCoordinator )

Is NSPersistentStoreCoordinator executeRequest without any context, safe to fetch _objectIDs_

We have a background thread that needs to do some fetching.. but it doesnt need any data -- only the objectIDs
originally we did this using a specific newly created blank managed context just for this.
NSFetchRequest *request = [DKDocumentDetails requestAllWithPredicate:predicate inContext:ctx];
[request setResultType:NSManagedObjectIDResultType];
self.objectIDs = [DKDocumentDetails executeFetchRequest:request inContext:ctx];
...
but recently I found out, I can also do this on the PST itself, without any context AS I dont want Managed Objects, but only IDs
NSFetchRequest *request = [DKDocumentDetails requestAllWithPredicate:predicate inContext:mainctx /*used in the wrong thread but only for getting entity description*/];
[request setResultType:NSManagedObjectIDResultType];
NSError *error = nil;
self.objectIDs = [pst executeRequest:request inContext:nil error:&error];
...
so in my tests it never crashed and in the docs I dont see why it shouldnt work either... I mean I dont get unsaved stuff and I cannot get objects, but used this way...
It is faster and looks elegant but is it safe or not?
I've been thinking about your question all day. Here is what I've come up with. As others have pointed out, NSPersistentStoreCoordinator objects are not thread safe. When a bunch of NSManagedObjectContext objects on various threads use the same NSPersistentStoreCoordinator, they do so by locking and unlocking the NSPersistentStoreCoordinator.
However, you are worried about just reading data, and thread safe NSManagedObjectID data at that. Is that ok?
Well, the Apple documentation On Concurrency with Core Data mentions something similar to what you are doing:
For example, you can configure a fetch request to return just object IDs but also include the row data (and update the row cache)—this can be useful if you're just going to pass those object IDs from a background thread to another thread.
Ok, but do we need to lock the Coordinator?
There is typically no need to use locks with managed objects or managed object contexts. However, if you use a single persistent store coordinator shared by multiple contexts and want to perform operations on it (for example, if you want to add a new store), or if you want to aggregate a number of operations in one context together as if a virtual single transaction, you should lock the persistent store coordinator.
That seems to be pretty clear that if you are performing operations on a persistent store from more than one thread, you should lock it.
But wait - these are just read operations, shouldn't they be safe? Well, apparently not:
Core Data does not present a situation where reads are “safe” but changes are “dangerous”—every operation is “dangerous” because every operation has cache coherency effects and can trigger faulting.
Its the cache we need to worry about. That's why you need to lock - a read in one thread can cause data in another thread to get messed up through inadvertent cache changes. Your code never gave you problems because this is probably really rare. But its those edge cases and 1-in-1,000,000 bugs that can do the most damage...
So, is it safe? My answer:
If nothing else is using your persistent store coordinator while you read, yes, you are safe.
If you have anything else using the same persistent store coordinator, then lock it before you get the object IDs.
Using a managed object context means the locking is automatically taken care of for you, so its also a fine possibility, but it looks like you don't need to use it (and I agree it is nice to not make one just to get a few Object IDs).
From the NSPersistentStoreCoordinator docs:
Note that if multiple threads work directly with a coordinator, they need to lock and unlock it explicitly.
I would say that if you were to properly lock the PSC:
[pst lock];
self.objectIDs = [pst executeRequest:request inContext:nil error:&error];
[pst unlock];
That would be considered "safe" according to my reading of the docs. That being said, locking done internally by the MOC might be the most significant performance difference between the two approaches you have described, and if that's the case you might prefer to to just use the blank MOC as it would be less surprising when you or someone else encounter's the code later.
Related question:
Is NSPersistentStoreCoordinator Thread Safe?
There is no a good reason to NOT use a managed object context for this. The managed object context buys you a lot - it handles change management, threading, etc. etc. Using the persistent store coordinator directly loses a lot of this functionality. For example, if you have changes that have not been persisted yet to this store, you may miss them by using the persistent store coordinator directly.
Now you say the reason this is attractive to you is that you only want the managed object IDs. What it seems you really want is to find managed objects but not fire faults on them. You can do this with either a NSManagedObjectResultType or a NSManagedObjectIDResultType on your fetch request. In the case of the NSManagedObjectResultType, you would just access the objectID on your fetched objects, which will not fire a fault - thus not "getting data". This can have some performance advantages if the row cache is already populated, etc.
With all of that said, why not just use parent-child contexts to solve this?

Have mergeChangesFromContextDidSaveNotification: complete before listening NSFetchResultsControllers pounce and update themselves

I have an NSFetchedResultsController (FRC) backing a Table View. By definition, it's listening in for changes in it's managed object context (MOC) relevant to its defining fetch request.
While this table view is visible, I may have a small import (like ~ dozen records) I run, with managed objects that will be relevant to the watching FRC.
I run the import on a peer MOC to the one backing the table view. Both MOC's are NSMainQueueConcurrencyType, with a common parent MOC of NSPrivateQueueConcurrencyType.
As soon as the import completes with a save, my previously setup notification fires a handler, that simply does a mergeChangesFromContextDidSaveNotification:
- (void)scratchpadContextDidSave:(NSNotification *)saveNotification {
log4Info(#"Default Context now merging changes from Scratchpad Context save notification.");
[self.defaultContext mergeChangesFromContextDidSaveNotification:saveNotification];
}
Question:
How do I get the mergeChangesFromContextDidSaveNotification: to complete before any listening FRCs get notified?
Motivation
The FRC's new data coming in acts as a trigger of sorts in my application, which is fine, as long as all of the merged changes are available. My FRC acts immediately, not having complete information.
I'm going to remove logic needing the FRC to be patient this way, but it got me concerned that I'm not aware of a way to "lock" change notifications from going out until the entire merge operation is complete.
idStar,
A way to force a synchronous to all -save:s is to have them serialize their merges through a private serial queue where the last item actually performs the merge to the main MOC. Just because an item is saved to the persistent store, doesn't mean it has to be merged right away. You will have to merge the userInfo dictionaries yourself before submitting the dictionaries to the main MOC.
Andrew

NSManagedObject changed properties after save

Is it possible to find out which properties were saved on a managed object after the save occurs? For example, I have someone listening for managed object context saves, (NSManagedObjectContextDidSaveNotification) and I want to know which properties on the objects were saved.
The NSManagedObjectContextDidSaveNotification does contain all three bits of information you would need to sync with a server. Check the [notification userInfo] and you will find three sets inside: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey
If you want to know what properties on an entity have changed that would require that you track them yourself using KVO. I would recommend against this as the odds of that level of effort being worth it over just pushing the entire object up to a server are slim.
Update #2
On further poking around:
From the NSManagedObjectContextWillSaveNotification you could loop through each set and reference the changedValues method. You could keep a reference to that dictionary until after you receive the NSManagedObjectContextDidSaveNotification and then process the changes. Still sounds very heavy to me.
Update
What is your end goal?!?!
If you are trying to figure out what to push to a server then being at the attribute level is too low. You should be syncing at the entity level.
If you are just trying to keep some internal consistency inside of your application then you are thinking way, way too low level. This is a solved problem. Core Data solved it.
Why don't you get them when they are about to be saved. Subscribe to NSManagedObjectContextWillSaveNotification and check insertedObjects, updatedObjects and deletedObjects of the NSManagedObjectContext.
Update:
Even easier, get the user info of the NSManagedObjectContextDidSaveNotification
From the documentation:
Typically, on thread A you register for the managed object context
save notification, NSManagedObjectContextDidSaveNotification. When you
receive the notification, its user info dictionary contains arrays
with the managed objects that were inserted, deleted, and updated on
thread B.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1
Here's the solution I settled with. I have one singleton class that is notified when a context saves. The NSManagedObjectContextWillSave notification tells me which things have changed so I store them in a dictionary with the key being the context that saved. Then when I get the NSManagedObjectContextDidSave notification, I check the dictionary for the associated context. Finally, I remove that entry from the dictionary. Does that seem reasonable?

'No database channel is available'

I have an app which connects to the internet and stores data in an SQL database. I tested with iOS4, it works completely as it should. When I upgrade to the new version though, I get an NSInternalInconsistencyException, with this as the reason:
'_obtainOpenChannel -- NSSQLCore 0x951a640: no database channel is available'
From what I can gather, my database is being accessed by something when it shouldn't be, though I can't understand where or why.
Can anyone help me locate and properly diagnose my problem?
I found something for this one:
I got the error (among some other ones, seemingly randomly appearing) while I was accessing a managed object's relationships in a different thread than the one the managed context was created in. There have been some changes with respect to concurrent access to managed objects in iOS5 (see here http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-CoreData/_index.html#//apple_ref/doc/uid/TP40010637) - and although the doc states the default behaviour should be as pre-iOS5 it apparently is not true, my code did work without problems in iOS4.2.
For now, my workaround was to do all the relationship-access thingies in the main thread, store the data in an array, and access the data I need in the other thread via that array. No more errors at least. This is not the 'nice' solution I suppose, as I should (and will) change the way I concurrently access managed objects, but I'm not going to change that in a hurry now.
This default concurrency type for NSManagedObjectContext is NSConfinementConcurrencyType, which means it can only be used by a single thread. From the documentation:
You promise that context will not be used by any thread other than the
one on which you created it.
You can instead create a managed object context that is backed by a private queue for multithreaded use:
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]
To use the managed object context from a different thread, use performBlock: (asyncronous) or performBlockAndWait: (synchronous), e.g.
__block NSArray *results;
[[self managedObjectContext] performBlockAndWait:^{
results = [[self managedObjectContext] executeFetchRequest:request error:&error];
}];
// do something with results
The documentation says you don't need to use the block API from the thread that created the managed object context.
Another option is to create separate managed object contexts for each thread.
See the iOS 5 release notes for more info.