My app sometimes inserts objects into the managed object context that are not meant to necessarily be saved. For example, when I launch an 'add entity' modal, I create a managed object and assign it to the modal. If the user saves from that modal, I save the context. If he cancels, I delete the object and no save is necessary.
I have now introduced an 'import' feature that switches to my app (using a URL scheme) and adds an entity. Because one of these modals might be open, it is not safe to save the context at this point. The transient object created for the modal will be saved, even if the user cancels, and there is no guarantee that the deletion (from the cancel operation) will be saved later - the user might quit the app.
Similarly, I can't simply save whenever my app quits. If the modal is open at that point, the temporary object will be incorrectly saved.
I am looking for a strategy to handle this architecture. I am considering some 'flagging' solution that allows me to identify the imported entities. When the user user quits the app, I will check if there are any unsaved changes to the context. If so, I will filter out everything except the imported entities, and then save. I have no idea if this is possible (selective saving) or a good idea.
Kevin and Andrew's comments (and the linked article) were enough to get me going. I got some follow-up advice in this question.
In summary, I am using a child context to create the transient object, and then merging it into the main context. In effect, I only need the temporary context as place to insert the object - if it was possible, for example, to create it outside of an insert message, I could do so and then insert it straight into the main context on confirmation.
Related
We have three separate apps which are in same App Group and access the same CoreData store. Problem is that when I change something in item in NSOrderedSet from relationship in managed object, save go to another app where refresh is performed, changed data are not there.
We are using NSPersistentContainer and only one context in each app, container.newBackgroundContext() saved to property in singleton. For each app when app goes to BG save() is performed on that context and when app goes to FG refreshAllObjects() is called on the context.
When I change some basic attribute in managed object it is changed properly in another app. But when I change some property in item from NSSet which is a relationship on managed object this change is not visible in another app.
While I was debugging I tried to call fetch but it also provides only old data. Only when I called context.reset() and then fetch again it returns valid new data.
Problem is that I cannot use reset on whole context because I will lose all registered objects in app.
Is this valid behavior or bug that referenced objects changes are not applied when refreshAllObjects() is used?
Is there any way how to force fetch request to get data directly from the database and not cached one from context?
I found one solution to my problem. I was able to force fetch to fetch directly from persistence (not from context) by setting shouldRefreshRefetchedObjects property of NSFetchRequest to true.
I was not able to find a solution to just refresh already fetched objects - but this way I was able to get fresh data using new fetch at least.
I have an object graph which represents the state of my (first) iOS app. I've implemented NSCoding for each of the objects so I can use a keyed archiver. I have the archiving and dearchiving working fine. But I'm left with a rather basic question: When should I archive things?
Is it safe to only call it when I get an applicationDidEnterBackground message from my app delegate? Or should I pesist things everytime the user does something "significant" in the interface (like dismiss some view where data was entered, etc.)? What are the best practices for this?
I found the answer to my own question in this document:
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforHandlingAppStateTransitions/StrategiesforHandlingAppStateTransitions.html
Here is the relevant quote:
Important: Always save user data at appropriate checkpoints in your app. Although you can use app state transitions to force objects to write unsaved changes to disk, never wait for an app state transition to save data. For example, a view controller that manages user data should save its data when it is dismissed.
I want to ask for a safe way to clear subEntities in coredata.
I have my a many-to-many relationship like this: Product *<->* Product. Therefore, I've got to create a subEntity to hold some special values between (sortPosition, groupName.....).
So it's like this: Product *<->1 ProductSubEntity 1<->*Product.
When I download products from server's API, the easiest way to update correctly correspond to the server's result is:
Remove all child relationship ([self removeProductSubEntities:self.subEntities]).
Add sub from server's result.
Result: There'd be a lot of subEntity in coredata (which won't hold relationship to any product), and this might take storage/memory/cpu when CRUD (I think?). But I can't actual delete the subEntity (in case it's being hold reference to as an viewController's Object somewhere, and it might cause crash: access to a deleted object).
QUESTION:
How can I clear those sub entities (might occur sometimes) if:
No relationship to any product.
No actual reference from anywhere (any viewControllers or objects)???
P/S: I'm thinking of implement a batch delete when terminate app. Could that be consider a safe solution?
I don't consider this to be a datastore issue, rather a UI update issue. You should delete the objects from the datastore when you don't need them any more and you should update the UI accordingly.
1 thing you didn't mention is re-use. It's possible that your download may be an update to an existing item, which you could find and update, then life is easy all round. Arguably everything below still applies in this case though as your UI might not update to reflect changes and you may need to refresh the managed object.
For the UI update it's generally wise to observe the datastore for changes, usually with an NSFetchedResultsController. If you're doing this then your UI would automatically update itself with the changes.
If you're explicitly passing entity instances around then you should have some way to trigger an update explicitly, and exactly how that works depends on your UI. Generally speaking you'd be doing something like posting a UINotification to tell the system that the datastore changed and they need to re-validate their data objects. For the UI you shouldn't be showing now-dead objects to the user, and in your question where you talk about not deleting to avoid crashes, it's probably worse to allow the user to update invalid objects and just quietly not telling them that their updates won't be saved. When the notification is received you may want to pop a (some) controller(s) off the stack, or re-query the datastore for the new data to be displayed.
If for some reason you don't want to do the above, then yes, you can query for all of the entities with a nil relationship and then batch delete them. This should be done on a background thread just like data loading and I'd recommend doing it on app load instead of close (because you won't have so many view controllers loaded and the ones that are should all have only valid references now...).
If I add entities to a moc and I execute a fetch request on that moc before its document gets a chance to save, will my fetch contain the newly added entities? (I'm unsure how to test this out because of the auto-save feature)
I guess you're using UIDocument when you say 'auto-saving'. If you aren't, then there is no 'auto-saving' (except when using Apple template code and the app delegate saves before termination).
When you create the fetch request, you can choose whether it should include unsaved items using the includesPendingChanges property. It defaults to YES so by default you will see unsaved items in the results.
I am pretty new to iOS and I am currently designing the "Create a new event" page for my app.
Some info:
A user can click on create a new event and then press back to cancel creating this new event. (I am using UINavigationController)
1 video can be attached to a particular event.
A user can create a new event, record a video, then decides not to go ahead with saving it (by pressing back)
Each event must generate a uuid. This uuid is required to prevent entity collision between different clients creating events and submitting them to the server.
Each video captured is named uuid.mov for storing into the file system before the event is uploaded to the server.
Coming from a rails background, I am used to doing the following:
When the user click on "Create new event page", an event is created but not saved to the db.
The user then enters attributes to the page and decides if he/she wants to commit
When commit take places, the event is attributed with the information provided. The event is then saved.
However, I don't believe Core Data has an API that allows the developer to create an entity without saving it. Currently, I am doing something like this:
When the user visits the "create new event page", an empty object is created and stored into the db. The reference to the event instance is passed to the Controller of the create a new event page.
When the user has inputted all the information and shot a video (the uuid is created at awakeFromInsert and is set during 1), saving is merely setting the entity with the required attribute.
If the user decides to cancel creating an event, I am checking whether cancel is pressed. if it is, both the event and the video file is deleted.
Code to check if Cancel is pressed.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (![[self.navigationController viewControllers] containsObject:self]) {
This is a bit complicated to be honest. Do you guys have a better suggestion?
It actually works as you described Rails working: new objects aren't saved to the persistent store until you commit them (via NSManagedObjectContext's save: method).
See the Creating and Deleting Managed Objects section of the Core Data Programming Guide. Excerpting...
Simply creating a managed object does not cause it to be saved to a
persistent store. The managed object context acts as a scratchpad. You
can create and register objects with it, make changes to the objects,
and undo and redo changes as you wish. If you make changes to managed
objects associated with a given context, those changes remain local to
that context until you commit the changes by sending the context a
save: message. At that point—provided that there are no validation
errors—the changes are committed to the store.