Crash when deleting object from Core Data and toggling view controllers - ios

I am making a NSFetchRequest for a NSManaged Object on my initial screen. I sometimes have a crash in a scenario when I :
switch to another view controller within my tab bar controller
make another fetch request with the same managed object type
delete a common managed objects which also appears in my initial VC's fetchrequest. The VC contains a table view.
save the managed context
toggle to the first VC, and reload the data
I am not using NSFetchResutltsController to manage these returned objects. The crash happens when my tableview reloads. I do make another request, and expected the deleted objects not be returned, but it does. When my cells are trying to read a property of the deleted object, it reads uninitialized and crashes. This happens about 1 out of 5 times when toggling between the 2 VCs. I am using performAndWait in all of my CoreData functions.
Is there a way to decouple the the relationship of the Managed Objects between the two screens? If not, how can I get my fetch request in the first VC, not return the objects that were deleted in the second VC, keeping them in sync?

A NSManagedObject is not like other other objects. It does not contain any information itself. It has a pointer to its context and an objectID. When you access it's properties it forwards the request to the context to get the information that it needs. So when an entity is deleted from the context the managedObject stops working and causes a crash. This is why in general I think it is a bad practice to EVER keep a pointer to a managedObject and ALWAYS access them using a fetchedResultsController even if only for one object, and only do a fetch if the managedObjects results are discards right afterwards.
There are two possible solutions, which you hinted to in your question. Either you can copy the values out of the managedObject, or you can use a fetchedResultsController. If you copy the values then it will appears as normal even after the entity is deleted. If you use a fetchedResultsController then the fetchedObjects property will be never contain deleted object, and the object will be inaccessible after it is deleted.
I would recommend using a fetchedResultsController. You don't need to be afraid of it. It is not a large overhead and it reasonable to use even if you are fetching only one object.

Related

Why does storing a reference to an NSManagedObject prevent it from updating?

This question is poorly phased but this can be better explained in code.
We have a Core Data Stack with private and main contexts as defined by Marcus Zarra here: http://martiancraft.com/blog/2015/03/core-data-stack/
We call a separate class to do a fetch request (main context) and return an array of NSManagedObjects:
NSArray *ourManagedObjects = [[Client sharedClient].coreDataManager fetchArrayForClass:[OurObject class] sortKey:#"name" ascending:YES];
We then do some processing and store a reference:
self.ourObjects = processedManagedObjects
Our view contains a UITableView and this data is used to populate it and that works just fine.
We change the data on our CMS, pull to refresh on the UITableView to trigger a sync (private context) and then call this same function to retrieve the updated data. However, the fetch request returns the exact same data as before even though when I check the sqlite db directly it contains the new data. To get the new values to display I have to reload the app.
I have discovered that if I don't assign the processedManagedObjects to self, the fetch request does indeed return the correct data, so it looks like holding a reference to the NSManagedObject stops it from getting new data from the main context. However I have no idea why that would be.
To clarify, we're pretty sure there's nothing wrong with our Core Data Stack, even when these managed objects are not being updated, other are being updated just fine, it's only this one where we store a local reference.
It sounds like what's going on is:
Managed objects don't automatically update themselves to reflect the latest data in the persistent store when changes are made via a different managed object context.
As a result, if you keep a reference to the objects, they keep whatever data they already had.
On the other hand if you don't keep a reference but instead re-fetch them, you get the new data because there was no managed object hanging around with its old data.
You have a few options:
You could keep the reference and have your context refresh the managed objects, using either the refresh(_, mergeChanges:) method or refreshAllObjects().
If it makes sense for your app, use an NSFetchedResultsController and use its delegate methods to be notified of changes.
Don't keep the reference.
The first is probably best-- refreshAllObjects() is probably what you want. Other options might be better based on other details of your app.
Try setting the shouldRefreshRefetchedObjects property of the fetch request to true. According to the documentation:
By default when you fetch objects, they maintain their current property values, even if the values in the persistent store have changed. Invoking this method with the parameter true means that when the fetch is executed, the property values of fetched objects are updated with the current values in the persistent store.

unattached (disconnect) Core Data entity from context

I'm using magical record for all my core data work.
everything works great, with the exception at times when I'm doing updates in the background I need to detach or disconnect the entity from the context.
for example
ButtonList = [Buttons MR_findAllSortedBy:#"listOrder" ascending:YES];
How would I keep the entity, but remove the reference to the context for the array ButtonList?
Thanks
This will only happen when you don't use a NSFetchedResultsController, or code that observe context changes and remove deleted objects from the UI to reflect the store state.
If you like the deleted objects to be removed from view as soon as your context finds out about the deletion, you would need to listen for "context did change notification" on your main context and look at the deleted objects set, if any of the deleted objects are part of your display array you will need to update your view accordingly (remove from array and update table. a NSFetchedResultsController also listen for context changes).
Another option:
Since you manage your tableview state by yourself (and not a fetched results controller) and
If you like the "buttons" to remain in view including their properties, you could:
Change your request to return dictionaries instead of managed objects (does not nullify on deletion):
NSFetchRequest* r = [Buttons MR_requestAllSortedBy:#"listOrder" ascending:YES];
[r setResultType:NSDictionaryResultType];
//This is your link to the data store and managed object (if you later need to fetch by or update if still exist)
NSExpressionDescription* objectIdDesc = [[NSExpressionDescription new] autorelease];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
[r setPropertiesToFetch:#[objectIdDesc,#"buttonName",#"buttonIcon"/*, and any other property you need for display*/]];
Now all is left to do is execute this request on any context you like (even in background) and get the array back to your table view controller.
The difference here is you get back dictionaries and not NSManagedObject array.
In Core Data there's no such concept of "detaching" an entity from a given context. You would have tons of problems in attempting to do something like that (e.g. move objects between contexts/persistent stores), especially when dealing with relationships.
I think you should refactor and design your application in a way that reacts proactively to any changes in the object(s) you are representing, e.g by removing the related table view cells (with NSFetchedResultsControllerDelegate callbacks), by automatically popping the detail view controller if present, etc...
I would not recommended recommend it but, if there are states for the object you're representing which are "transitory" and shouldn't be reflected in the UI, you could temporarily nil the NSFetchedResultsController delegate (I assume you're using that to display your managed objects) so that it doesn't receive updates and doesn't update the table view (for example). When the objects are ready to be displayed again, you can set the delegate back and perform a new fetch, so that the tableView gets updated (with automatic cell insertions, removals and updates).
If an object has been fetched from a managed object context, the only way to break its connection to the context is to delete the object and then save changes. There is no way to convert a fetched object into something that's not associated with its context. You could copy the data to some other object, but the one that you fetched is and will always be associated with the context that you got it from.
In your case, if you're deleting these objects in a background queue, you should not be using them any more. Or if you do need to use them, then you should not be deleting them. I can't tell from your question just what you're trying to accomplish here, but what you've described makes no sense.

NSArray with NSManagedObject's relationships faults on second display in UITableView

I'm parsing a bunch of data and mapping it to Core Data NSManagedObjects. I pass these to my UITableViewController in an NSArray which I use as the dataSource.
The NSManagedObjects are often linked to other NSManagedObjects with relationships. Most of these entities that are linked via a relationship have the content I need to display (depending on the relationship). Initially the UITableView displays the content no problem. As soon as I start scrolling and the cell is re-used or if I scroll back to the same location, the cell is blank (I'm just debugging right now, so only displaying content as a string).
I'm logging the NSManagedObject and get the following before scrolling:
<NewPost: 0xd5dcc00> (entity: NewPost; id: 0xd5dcc60 <x-coredata:///NewPost/t63931035-BB67-467D-8598-CAD8563BA5DC267> ;
data: {
group = "0xd5b7a50 <x-coredata:///Group/t63931035-BB67-467D-8598-CAD8563BA5DC265>";
newPostAttributedText = "0xd5e78d0 <x-coredata:///AttributedText/t63931035-BB67-467D-8598-CAD8563BA5DC269>";
newPostSection = "0xe85cc70 <x-coredata://3DE41B33-C64E-44C4-9F86-98DF3C6AD700/PostSection/p7>";
newPostCreator = "0xd297050 <x-coredata://3DE41B33-C64E-44C4-9F86-98DF3C6AD700/Person/p9>";
})
When scrolling and showing the same object I see the following in my log:
<NewPost: 0xd5dcc00> (entity: NewPost; id: 0xe8b3cc0 <x-coredata://3DE41B33-C64E-44C4-9F86-98DF3C6AD700/NewPost/p75> ;
data: <fault>)
Why does the relationship fault when I need to re-use the cell or re-display the same data?
Thanks
You need to ensure that each managed object's context is associated to either the main thread (thread confinement) or the main queue. (Note: a managed object has a property managedObjectContext)
If you access managed objects in the main thread, for example to render the content with UIKit, their managed object context MUST be associated to the main thread respectively the main queue.
Also, it makes sense to have only one context for the objects and its related objects.
Additionally, you need to ensure that your context (in this case the "main context") is actually up to date. That means, you possibly need to fetch objects which retrieves them from the persistent store if required, and it also updates the set of objects (when using a predicate).
This above is required for example when you have a context M (the main context associated to the main queue) whose parent is the root context (M -> root), where the persistent store is handled. Then, delete objects from a different context B (B-> root) and save it persistently. Now, objects in context Main might be deleted but they still exist as "registered" managed objects in that context, but are faults. Fetch context M (main context), in order to update it.
Note: You are better off using a NSFetchedResultsController in order to handle updates.
Caution: When using MagicalRecord, you may end up with a Core Data stack, where the "default context" handles the persistent store and is also associated to the main thread. Children contexts will execute on a private queue and will have set the main context as its parent. IMHO, this is suboptimal.
Take also a look here on SO NSOperation in iOS - How to handle loops and nested NSOperation call to get Images, for dealing with managed objects.
I had the same problem and I solved it by setting to NO the returnsObjectsAsFaults property in NSFetchRequest object:
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults:NO];
Try it!
I've had issues with my tables doing strange things when a cell is scrolled off screen and then back on. My usual work-around is to not reuse cells. Note that if you try doing this with very large tables, there may be some performance loss. The tables I've used this on are never more than twice the size of the screen, and I've never noticed any performance lag, but I imaging if you are doing it with 100s of rows or something you could run into trouble.
If you want to go this way, the general idea is:
Call a new method from viewDidLoad (or something similar) that creates all the cells for the whole table (on screen and off) - this method is usually pretty similar to whatever was originally being done in cellForRowAtIndexPath
Put the cells into an NSMutableArray ivar as they are made.
In cellForRowAtIndexPath, just return the cell from that array.
It's probably not the ideal solution, but it has worked for me pretty well in the past when my cells are misbehaving when they get reused.

iOS5 NSFetchedResultsController not getting delete updates

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.

NSFetchedResultsChangeInsert gets called twice for 1 insert with different object IDs

I'm losing my mind around this question.
So I have a Core Data setup in my iOS app done this way:
http://www.cocoanetics.com/2012/07/multi-context-coredata/
I then insert an object by creating a temporary MOC (as explained in the blog post) and perform saves on all 3 contexts in performBlock: methods.
In a view controller I have an NSFetchedResultsController and it gets notified that I did indeed insert a new object. The problem is that the NSFetchedResultsChangeInsert is fired twice and each time the object that is passed trough has a different objectID (it also is a different object instance in memory). What happens is that I then have 2 rows inserted in my table view but un the SQL database there is only one new. It then of course crashes when I scroll to the bottom of the table view.
If I also perform some updates on the object I get NSFetchedResultsChangeUpdate called only once and with the objectID that was passed in the second NSFetchedResultsChangeInsert call.
The first ID looks like this:
<x-coredata:///ReceivedMessage/t605BB9A7-A04E-4B89-B568-65B12E8C259A2>
The second (and all consequent ones) like this:
<x-coredata://02A917C5-850F-4C67-B8E4-1C5790CF3919/ReceivedMessage/p28>
What could this be? Am I missing out something obvious?
PS: I also checked if the notification comes from the same context, thread, etc. It does.
The two IDs you are seeing may very well represent one object. The difference between them is just that the first one is a temporary object ID, assigned to the object on creation, and the second one is the permanent object ID, assigned to the object when it gets stored to the managed object store (see NSManagedObjectID's isTemporaryID).
To work around this issue you could call NSManagedObjectContext's obtainPermanentIDsForObjects:error: just before you save the temporary MOC. This way the inserted object will have just one ID during the save propagation and the NSFetchedResultsControllerDelegate methods should get called just once.

Resources