Preamble: I'm using RestKit 0.21.0, but I've also tried 0.23.0.
I have a number of mappings where a plain old Objective-C object (let's call that a POOCO) contains an NSManagedObject. It seems that when doing GET requests on this root POOCO, the managed object is created under the mainQueueManagedObjectContext.
However when I do a PUT against the POOCO, when the response is loaded (the response is a copy of the object that was sent), the managed sub-object is created under a temporary NSManagedObjectContext, whose parent is the mainQueueManagedObjectContext.
A quick aside on re-fetching in RestKit:
If the request were composed entirely of NSManagedObject instances, the resulting objects would be "refetched" using (I think) the mainQueueManagedObjectContext. (IIRC, once all the object mapping is complete, the objects and sub-objects would all be re-fetched from the main MOC, replacing the objects that were created on the temporary MOC used for mapping.)
This refetching is done (as far as I can tell) because when a MOC gets dealloc'd, all of the managed objects that it manages become invalid. So we refetch the objects in a MOC to which myObjectManager.managedObjectStore maintains a strong reference, so that they remain valid after the temporary MOC goes away.
Because the root object in the mapping is a POOCO, any references it has to managed objects do not get refetched, so they remain attached to the temporary MOC, and become invalid once mapping is complete.
Has anyone else run into issues like this? Are there best practices you can suggest? Is it possible to tell RestKit, "refetch these managed objects using the mainQueueManagedObjectContext" or anything like that?
Managed object instances can't be passed between threads, and RestKit does the mapping on background threads. If the returned objects are managed objects then they will be transferred to the main thread for you. It could be argued that nested managed objects not being switched to the main thread for you is a bug / oversight in RestKit (that you could raise as an issue in github).
You can ask RestKit to use the main thread for some things (by explicitly creating the operations and running them, though this doesn't cover everything) but you really don't want to do that.
So, your main option is to have a method in your container object which you can call in the success block and which iterates through the contained managed objects and 'refetches' them - as you know that all the objects exist this can be done with objectWithID:, and the success block is called on the main thread.
The question actually has some misconceptions about when RestKit performs refetching. Under the circumstances described in the question, RestKit actually does return an instance of RKRefetchingMappingResult in the success block. Calling any of the accessors on that object (such as array or firstObject) actually does perform the refetching. The mistake was that I was never invoking any of these methods on the RKMappingResult.
My assumption was that, since the object I was sending in the PUT request would become the operation's targetObject, the object would be updated in-place by RestKit. This is why I never felt the need to do anything with the RKMappingResult. The assumption isn't technically wrong, but by accessing the object directly once the mapping was complete instead of using the RKMappingResult, I was skipping the refetching step.
I've proposed a change in this behaviour in an issue I've filed with the RestKit folks, to make refetching automatic in certain circumstances.
To summarize:
When making a request using RKManagedObjectRequestOperation (whether you specify that manually or RestKit selects this class for you), always make sure you exercise the RKMappingResult to ensure that the results are re-fetched from the correct NSManagedObjectContext.
Related
I'm trying to efficiently batch delete a lot of NSManagedObjects (without using an NSBatchDeleteRequest). I have been following the general procedure in this answer (adapted to Swift), by batching an operation which requests objects, deletes, saves and then resets the context. My fetch request sets includesPropertyValues to false.
However, when this runs, at the point where each object is deleted from the context, the fault is fired. Adding logging as follows:
// Fetch one object without property values
let f = NSFetchRequest<NSManagedObject>(entityName: "Entity")
f.includesPropertyValues = false
f.fetchLimit = 1
// Get the result from the fetch. This will be a fault
let firstEntity = try! context.fetch(f).first!
// Delete the object, watch whether the object is a fault before and after
print("pre-delete object is fault: \(firstEntity.isFault)")
context.delete(firstEntity)
print("post-delete object is fault: \(firstEntity.isFault)")
yields the output:
pre-delete object is fault: true
post-delete object is fault: false
This occurs even when there are no overrides of any CoreData methods (willSave(), prepareForDeletion(), validateForUpdate(), etc). I can't figure out what else could be causing these faults to fire.
Update: I've created a simple example in a Swift playground. This has a single entity with a single attribute, and no relationships. The playground deletes the managed object on the main thread, from the viewContext of an NSPersistentContainer, a demonstrates that the object property isFault changes from true to false.
I think an authoritative answer would require a look at the Core Data source code. Since that's not likely to be forthcoming, here are some reasons I can think of that this might be necessary.
For entities that have relationships, it's probably necessary to examine the relationship to handle delete rules and maintain data integrity. For example if the delete rule is "cascade", it's necessary to fire the fault to figure out what related instances should be deleted. If it's "nullify", fire the fault to figure out which related instances need to have their relationship value set to nil.
In addition to the above, entities with relationships need to have validation checks performed on related instances. For example if you delete an object with a relationship that uses the "nullify" delete rule, and the inverse relationship is not optional, you would fail the validation check on the inverse relationship. Checking this likely triggers firing the fault.
Binary attributes can have data automatically stored in external files (the "allows external storage" option). In order to clean up the external file, it's probably necessary to fire the fault, in order to know which file to delete.
I think all of these could probably be optimized away. For example, don't fire faults if the entity has no relationships and has no attributes that use external storage. However, this is looking from the outside without access to source code. There might be other reasons that require firing the fault. That seems likely. Or it could be that nobody has attempted this optimization, for whatever reason. That seems less likely but is possible.
BTW I forked your playground code to get a version that doesn't rely on an external data model file, but instead builds the model in code.
Tom Harrington has explained it best. CoreData's internal implementation apparently requires to fire fault when marking an object to be removed from the persistent store, just like it would if you were accessing a property of the object. As explained in this answer, "An NSManagedObject is always dynamically rendered. Hence, if it is deleted, Core Data faults out the data".
This seems to be the normal behaviour at least for the moment being, not really an issue.
There is a crash occurring within my app that only happens quite rarely (maybe once every 30 runs). The error code contains a strange selector name _nilOutReservedCurrentEventSnapshot__ which I haven't been able to find any documentation for at all. Here is the feed from my console:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFType _nilOutReservedCurrentEventSnapshot__]: unrecognized selector sent to instance 0x157b51e0'
*** First throw call stack:
(0x2358810b 0x22d2ee17 0x2358d925 0x2358b559 0x234bbc08 0x24cbf445 0x24ca4d99 0x249bec 0x245c90 0x19b68c 0x24a5c97 0x24b05ab 0x24a8ef9 0x24b1a8d 0x24b18e7 0x232bfb29 0x232bf718)
libc++abi.dylib: terminating with uncaught exception of type NSException
If anyone is able to shed some light on the meaning of this phrase _nilOutReservedCurrentEventSnapshot__`, that would help me immensely. A screenshot of the location of the crash is below:
Unfortunately there isn’t much you can find about _nilOutReservedCurrentEventSnapshot__
online..
It is probably related to the snapshot lifecycle of a managed object.
When Core Data fetches an object from a persistent store, it takes a snapshot of its state. A snapshot is a dictionary of an object’s persistent properties—typically all its attributes and the global IDs of any objects to which it has a to-one relationship. Snapshots participate in optimistic locking. When the framework saves, it compares the values in each edited object’s snapshot with the then-current corresponding values in the persistent store.
The fact that _nilOutReservedCurrentEventSnapshot__ is not documented means that this type of behaviour is not supposed to happen.
What we do know is that it is a function of the NSManagedObject class.
Therefore The error unrecognized selector sent to instance was caused because _nilOutReservedCurrentEventSnapshot__ was called on an object that was not an NSManagedObject, because the NSManagedObject was deallocated and something else now fills its memory. This is fact.
Context isn’t given from the question about the nature of the app and its CoreData setup but it is inferred that it is using a parent-child concurrency pattern. This is important.
[Image retrieved from here]
From all the questions asked on stack overflow that I can find about this error, it appears they all use the parent-child concurrency pattern.
It is entirely possible that the issue is caused by the adoption of this pattern with improper implementation or handling of ManagedObjects.
A Situation where a parent-child context may be used is when syncing cloud data or handling changes made when a user edits something with the option of discarding or undoing the changes made, which can be done on a background thread.
It is also mentioned in the question that this is a rare occurrence and doesn’t happen every time. Meaning that the contexts are probably fine until a certain save or change is made and somehow the contexts become out of sync, and performing another save crashes the app.
From the docs on Synchronising changes between contexts:
If you use more than one managed object context in an application, Core Data does not automatically notify one context of changes made to objects in another. In general, this is because a context is intended to be a scratch pad where you can make changes to objects in isolation, and you can discard the changes without affecting other contexts.
Changes will be sent to the parent context when the child is saved but not if the parent has been changed separately. It is strongly adviced you never change the parent context; only through saving the child context and propagating the changes from there.
Possible reason for crash
There are several things that may cause this but two that stick out are:
1. deallocation of ManagedObject from reference that is disposed of due to dismissing a modal view in an iOS application or sheet in an OSX application.
possible solution: set the retainsRegisteredObjects property of the child context to true before fetching ManagedObjects (and then setting to false once the contexts have been saved to avoid further potential memory leaks). warning!
eg. ctx.retainsRegisteredObjects = true
2. Unhandled changes to a context.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/ChangeManagement.html#//apple_ref/doc/uid/TP40001075-CH22-SW1
Consider an application with two managed object contexts and a single persistent store coordinator. If a user deletes an object in the first context (moc1), you may need to inform the second context (moc2) that an object has been deleted. In all cases, moc1 automatically posts an NSManagedObjectContextDidSaveNotification notification via the NSNotificationCenter that your application should register for and use as the trigger for whatever actions it needs to take. This notification contains information not only about deleted objects, but also about changed objects. You need to handle these changes because they may be the result of the delete. Most of these types of changes involve transient relationships or fetched properties.
Possible solutions here are:
Use block methods on your contexts. refer to callback function at bottom of linked file.
Make sure to use objectWithID when passing around ManagedObjects between contexts.
Taking appropriate action on object change using a NSManagedObjectContextDidSaveNotification trigger.
removing all strong references to a ManagedObject. It may be possible that a strong reference was made to a ManagedObject used outside of the scope of the given code and so when the parent deallocated the ManagedObject and the child didn't, on a future save the child performed another save on that ManagedObject and then the parent context tried saving the ManagedObject which was deallocated.
"it is a good pattern to try to fetch objects, modify them, save them and reset the edit [child] context"
Because there is no other information here to deduce what is causing the error, and I can’t reproduce it.. I would suggest trying to find where your ManagedObject is deallocated using Zombie profiling in Instruments.
If a message is then sent to one of these deallocated objects (which are now NSZombie objects), the zombie is flagged, the app crashes, recording stops, and a Zombie Messaged dialog appears. You can then examine the retain and release history of the zombie object to determine exactly where the problem occurred.
https://github.com/iascchen/SwiftCoreDataSimpleDemo/blob/master/SwiftCoreDataSimpleDemo/CoreDataHelper.swift
Hopefully then someone can shed more light on what caused the issue.
+++++++++++++++++++++++++++++++++
Side Note:
parent/child contexts could result in stuttering from the UI - “every fetch request and every save operation will fully block the main thread while data is read or written to/from disk”. Which is because every fetch is pulled through the parent context (on the main thread) to the child.
supporting SO answer
It is recommended that if you are using this approach that you rethink the design to something like two independent managed object contexts connected to the same persistent store coordinator Which might 'avoid' the issue at hand in future from happening.
[Image retrieved from here]
You can see the drastic performance differences in the linked article
_nilOutReservedCurrentEventSnapshot__ is a private method on NSManagedObject internally to flush attributes and values from a Core Data object. You can see it in the private header for NSManagedObject here.
__NSCFType is an private wrapper for Core Foundation types used internally by the Objective-C runtime. You can learn more by looking at the private header here, but there isn't much to see.
Without the full backtrace, it's hard to debug the specific issue. The way I see it, there could be two culprits:
The parentObject of your context is somehow invalid.
You're trying to save a Core Foundation object as a property on an NSManagedObject which isn't expecting it.
A wild guess.. Is there any chance to call this save method from a different thread than the actual thread who created the context? For safer side, always execute Save: operation from perform / PerformBlockAndWait.
I'm trying to implement some basic UIManagedDocument import/export functionality into my app, mainly for dev so that I can easily inspect the document contents and more crucially preserve a set of test data when I start trying to iterate on my CoreData models.
All I am trying to do is load some JSON data from a local file and inject it into my apps UIManagedDocument. The UIManagedDocument's ManagedObjectContext contents are visualised in my app using some Core Data Table View Controllers from the Stanford iOS courses.
I thought I'd try to write this with some threading to keep the UI responsive and to learn how to do it. So I've done something like this
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext
} );
At first I thought this was working. No errors, asserts, crashes triggered and upon looking at my CoreData TableViews I could see the newly added objects in my UI. Unfortunately the newly added objects were seemingly never saved back to the store. I even hooked up to listen to the NSManagedObjectContextDidSaveNotification from my UIDocument's managedObjectContext and saw it wasn't triggering on pressing the home button, like it usually does if it has some changes performed in the app with my UI pending. Infact even doing these operations in the UI wouldn't cause the notification and saving to occur so it was clearly not happy.
I unrolled the code from within the background queue and ran it on the main thread synchronously and everything worked ok, the new data was saved correctly.
I started reading about the complexities of threading and coredata, the documentation seemed to suggest using the UIDocument's ManagedObjectContext's parent ManagedObjectContext to perform operations on in the background so I tried doing the same code again using this parent context, so as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext parent ManagedObjectContext
} );
This time for some reason the CoreData TableView controllers no longer updated to show the newly injected objects. Even after explicitly calling save on the parent context, nothing appeared. However on quitting the app and reloading the app the newly injected objects did seem to be added correctly. Interestingly at this point i'd left a fetchrequest with a cachename specified and that threw up an error on this first run of the app after injecting the objects this way. I guess somehow the way the object had come from the parent context directly perhaps invalidated the cache somehow, that's still something I don't fully understand. Even changing the cache to nil didn't fix the issue of the table views not updated the same session as when the objects were injected into the parent context.
Looking elsewhere I've seen some uses of the managedObjectContext performBlock suggested. Another case where someone has said you must call
[document updateChangeCount:UIDocumentChangeDone]
after all changes to ensure the saving is performed, or perhaps using
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
instead. Though elsewhere I've seen mentioned that saving should be enough to push context contents through the hierarchy. Does saving only work from child -> parent and not from parent -> child.
Or am I just doing it wrong?
Anyone's time and help is really appreciated! Cheers.
please look at the 'Parent/Child Contexts' section in the Multi-Context CoreData.
Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.
I do saving to the PSC n following way:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
NSError* error;
if (self.managedObjectContext.hasChanges)
{
[self.managedObjectContext save: &error];
}
// Save main context
dispatch_sync(dispatch_get_main_queue(), ^
{
[[AXContextHelper sharedInstance] saveMainContext];
});
}
After looking into all the suggestions I could find for similar problems, none of them seemed to help.
The scenario i was describing in the end had the store stuff to file handled ok, but the UI not updating. At the time when I do this import/export I'm in a view ontop of the core data table view controller that doesn't update when I inject all these JSON objects, so in the end all I did was force that controller to re-fetch it's data. I believe the NSFetchedResultsController is meant to monitor the managedObjectContext and update the fetch request as required. For whatever reason, this wasn't working.
With the force re-fetch called, everything seems to work ok. The newly injected entities appear in my tables, and are saved to the store files.
Either there are some bugs in these objects, or I'm still using it wrong but I see other problems... my import/export data both work now on background threads with the parent context. What I've noticed today is that I can use the UI to properly insert and edit some objects using the child context on the main thread. I don't deliberately call anything at the moment after making these edits so i guess the edits are are still pending till core data decides to save. If i then go to my export feature and use it using the parent context then i find the newly edited or inserted objects aren't there.
Clearly there's no messaging happening in time from child -> parent, and messaging from background thread edits on the the parent itself just doesn't seem to work.. I never get any notifications for the edits i perform in a thread on the parent context, even though they seem to work. If I force the save to happen via the documents then the passed in dictionaries show no edits being made, even though the edits are saved correctly to store files somehow.
I guess i now need to force my document to save after every operation, or at least before every operation i'm about to do on another thread to ensure the parent/child are both in sync.
Still the problem I originally described I've managed to get around, but I'm still left wondering quite how I should be using the parent and child context of a UIManagedDocument to avoid encountering these issues.
I have set up AFIncrementalStore to grab objects from a JSON service over the network and set its persistentStore to be an SQLite database. This all works fine.
Now what I want to do is add objects to that SQLite database out-of-band (from something other than the web service the AFIncrementalStore is pointing to), and have those additions reflected in the fetched results controllers created from the original managed object context.
I've created a managed object context with the original MOC as its parent and I can add objects to that and they're seen by the fetched results controller. But they're not saved to the AFIncrementalStore's SQLite db. Interestingly, AFIncrementalStore is seeing these objects as it was trying to save them back to the JSON service and complaining the correct endpoint didn't exist (I fixed this by overriding requestForInsertedObject:insertedObject to return nil.)
Anyone know how I achieve this?
If you don't want to POST objects to the server you have to override methods
requestForInsertedObject:insertedObject
requestForUpdatedObject:updatedObject
requestForDeletedObject:deletedObject
Then when you call context's save: method your objects must be saved in the database. I'm using similar logic when I'm doing CRUD operations offline and it is working fine.
It sounds like you have already found most of the answer. AFIncrementalStore checks for a nil response from requestForInsertedObject: in your AFRESTClient subclass. If that method returns nil, AFIS creates a permanent ID for the object, stores the object in the backing store and doesn't try to send it to the server again. This is all in the first section of executeSaveChangesRequest:withContext:error:.
Are you always calling save: on the parent ManagedObjectContext? If not, that would be the other reason it's not storing the object in SQLite. But then it shouldn't be trying to POST the object to the server either.
I have a NSFetchedResultsController tied to my main managed object context. It is in charge of keeping data for a table view in my main view.
I have an NSOperation running on a background thread that refreshes/deletes the managed objects that the fetched results controller is keeping track of. I create a child context (private concurrency type / parent = main managed object context) in the nsoperation and insert/delete objects. When it is finished, it saves its context, as well as the parent context.
What's interesting and very frustrating is that this works fine in iOS 6. When I insert or delete objects, my fetched results controller is notified of the changes and everything works as expected. However, on iOS 5, everything works except for deletes. The fetched results controller is not notified of a delete. However... if I manually refresh the fetched results controller (making it nil and refetching the same predicate) then it will show the expected result.
Also, when I register for change/save notifications on the main context, I can see that the user info dictionary contains the objects that I've deleted... even in iOS 5!
One issue that I thought it may be, but I don't think holds because I've removed the factors, is that this object is in a many to one relationship with another object. The object I am deleting/inserting is an "employee" and it has a relationship with a "department". The employee is set to nullify and the department is set to cascade.
As I said, this works fine in iOS6 but not in iOS5.
Any tips would be very helpful.
This bug is due to saving to the persistent store. This child context saves itself, then calls perform block on it's parent, the main managed object context. When the main managed object context saves, it triggers a background context to write to the persistent store. When I removed the background context save, the fetched results controller updated as expected.
Something interesting that I found that was probably causing this was that the managed object was leaking every time I tried saving to the store. Not exactly sure how to fix this yet, but it's good to know the reason for it.