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.
Related
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.
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.
I have an iOS app with master and detail layout.
In master, I managed my own NSManagedObjectContext, and also detail is, by this way:
NSPersistentStoreCoordinator *psc = ((AppDelegate *)UIApplication.sharedApplication.delegate).persistentStoreCoordinator;
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setPersistentStoreCoordinator:psc];
In master, I display list that can be clicked by user to show the detail in detail layout.
Upon filling the detail by user, user can save the detail by clicking on button there.
However, I am trying to understand this:
Since there is a save button in detail, the save method will save the detail with detail's context and call the load list in master
Load list will remove all the NSMutableArray of the list [_activities removeAllObjects]; and re-fetch the data from Core Data and reload the tableview
Done that but the the re-fetch function seems to use old data and not the latest.
Why does re-fetch the data doesn't work if I use same context?
You are using outdated APIs to create your managed object contexts. After creating a context, instead of assigning the persistent store, you should set the parentContext.
If you display a "list", you should be using a context that is of type NSMainQueueConcurrencyType. The best way is a NSFetchedResultsController which will also help you manage memory and performance, and greatly simplify the updates you need for your UI. (You would avoid the verbosity and complexity of merging the data "manually" via NSManagedObjectContextDidSaveNotification.)
NB: Resetting the context is a crude and inefficient method for the task you are trying to accomplish. Due to the fact that it affects the entire context, it could change the behavior in other parts of your app.
You have two contexts going on here which is why the data is not being refreshed. If you call [context reset] then you will find that the data will refresh.
Explanation:
An NSManagedObjedctContext is a "scratchpad" which manages the Objective-C representation of SQLite (or XML) data in memory. When you fetch data the context retains the managed objects that are created by the fetch. If you then fetch from another context that context reads the data from the persistent store and creates new managed objects based on what it finds.
ALSO: When you perform a fetch the context checks to see if a managed object representation is ALREADY in existence and then returns it if it is. This means that two contexts can get out of sync quite quickly.
There are several ways around this:
1) Calling reset on a context returns it to it's "baseState" which means all the managed objects are "forgotten" or released by the context. Therefore, another fetch will force the context to read the data directly from the store.
2) Implementing the NSManagedObjectContextDidSaveNotification (see here) will allow you to incorporate changes between contexts.
I have found this article very useful in the past.
In the view did load of a table controller I have the following
ButtonList = [Buttons MR_findAllSortedBy:#"listOrder" ascending:YES];
This is never called again, but evidently the data source that this is connected to does change int he background, which somehow propagates to the button list entity even though there is no new fetch. Is there a way to "unbind" this list after the initial call?
You will need to
make sure you save other changes
nil your local references to
the results
refetch with this same fetch request.
Core Data doesn't do this automatically for you. For something more automatic, look at NSFetchedResultsController
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.