When to Persist Object Graph - ios

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.

Related

Sending a dictionary from iOS app to WatchKit - watchOS2

My goal is to send a dictionary to the watchKit from iOS app prior to the watchKit's app launch. I'm using interactive messaging (sendMessage) to quickly transfer the dictionary.
The issue is - dictionary is created inside the MainViewController. If i declare the WCSession and activate it inside the MainViewController i can transfer the data to the watchKit on the simulator without any problem. But when i test the process on a real device - the iOS app never gets called.
Waking the app in the background is done by declaring and activating the WCSession inside the AppDelegate, but there's another blocker - i cannot create the dictionary - because multiple variables for its creation are declared inside the MainViewController.
I tried a third approach - wrapping the WCSession inside a singleton (suggested by Natasha the robot). The only drawback of this framework is that the Interactive messaging never works and wasn't ever tested by Natasha herself.
So i'm confused - what do i do to send the dictionary to the watchKit?
Thanks for any insights
You need to figure out a way to get the dictionary created outside of MainViewController. Perhaps you can write a class method in the controller that creates and returns the dictionary so that it can be used from both AppDelegate and MainViewController.
You should use a data store to hold your dictionary, then have it create its data based on the variables passed to it by the main view controller.
Once that occurs, you can use the WCSession manager to transfer the data store's dictionary.
I know Natasha covers these aspects in her tutorial. If you have a specific question as to how to do that, you'd really need to post code showing what you tried, along with a description of what's not working.
If the watch asks for data, but it has not been created yet, you need to return a "No data yet" reply so the watch can display a message telling the user to open the app and set the view controller's variables used for creating the data.
It really is better to separate and encapsulate responsibilities into these different components. The view controller shouldn't need to contain any code related to creating or transferring the dictionary.
Having said all that...
I cannot create the dictionary - because multiple variables for its creation are declared inside the MainViewController
This really sounds like an XY problem. You've been focused on the problem of "sending" this dictionary of large arrays that you have to create, when there's likely an easier way to accomplish what you're actually trying to do with this large dictionary in the first place.
For one, I'd wonder why you're sending that huge computed data set to the watch for it to do something with, instead of also handling that computation on the phone side, then sending a very small set of "results".
Perhaps you should describe the real Y problem you want to solve on the watch, instead of asking us for an X solution which may end up being unnecessary.

Changing Core Data when app starts

I am developing an iOS app which downloads xml data that are valid for 4 hours. I want to check validity and, if needed, update this data when my app starts.
I am using this xml parser to load data http://www.theappguruz.com/blog/xml-parsing-using-nsxmlparse-swift
Right now I am calling beginParse() and parsing data in AppDelegate.swift in function didiFinishLaunchingWithOptions. Is this the correct place to perform this background task?
In tutorial which I posted the guy does it in view controller but I want to use this parser to update coredata and I need it to run in background after app launches.
Thanks in advance
The professional way to do this would be have a function (or even a full class), to manage this download/parse data and asynchronously save in your core data, after that, you can inform the view that you have new content to load (or if there was an error or something).
About where to call the function, it depends...
If you should only show to the user the most updated information (like locking the screen whit a "loading..." or something like it), put the call in the first view controller, just because would be simpler to just call an completion handler to unlock and load the data.
But, if you can load the "old" information, just to be faster and refresh when the new content is available, i think that you can call in the appDelegate with no problem.

Detect deletion of Core Data

Extra info:
I have a messaging view in which I have a UITextView of which I save the text in the conversation's variable draft in the viewWillDisappear.
When the app tries to refresh the user's access code, they might get a "could not refresh" response, and the app logs the user out (only one device may be logged in at one time in this app).
In the logout method, I remove all app settings and empty out Core Data, then I set a new rootViewController and perform makeKeyAndVisible.
Question:
Now that you know all this, setting the rootViewController calls viewWillDisappear, which in turn tries to set the draft variable on a conversation that no longer exists in Core Data...
What can I do to solve this?
The simplest and fastest fix would be, when setting the draft:
if let context = conversation.managedObjectContext {
// you have a valid conversation, you can assign the draft
}
If the managedObjectContext is nil, this means the object has been deleted from Core Data.
EDIT
This answer provides a better way to detect if a managed object has been deleted from Core Data.
I would advise you to rethink the whole logout (clearing of resources) approach since yours is not going to scale in the future.

Approach to deal with Core Data save error (Cocoa error 1570.)

My app has a few properties (relationships) not optional in some entities in my Core Data model. I have the save method on the applicationDidEnterBackground of the App Delegate. Here lies the problem.
Some of my users keep losing data (save error) because when they are inputting data but not yet fill out the not optional property of the entity, a phone call or a push message comes. They pick up the phone or read the message, come back to the app and continue data input. However, my app has a passcode lock that is required every time the app launches and will take the user to the dashboard controller so they cannot resume the data input before the phone call/message.
So there is a managedObject with unfilled NOT Optional property in the managedObjectContext. The users continue fill out more data then close the app thinking the data has been saved. A few hours later or when they kill the app from the dock and reopen the app, all the data entered after the phone call/message are gone with this error:
NSLocalizedDescription = "The operation couldn\U2019t be completed. (Cocoa error 1570.)";
NSValidationErrorKey = propertyName;
How do I prevent this error from happening? I could think of 2 solutions:
1) Make all properties optional but I will have to change the core data model and do data migration. I have never done this and am afraid if the migration fails when it goes live. All the in app purchases are stored in core data.
2) Somehow check for the bad managedObject with unfilled NOT optional property from the context and delete the object before saving. How do I do this?
3) ?
Thanks,
You can actually catch and display the validation errors that occur is Core Data. Here's a sample of how that can be done: https://stackoverflow.com/a/3510918/171933
That way you could already validate the data before your users save (maybe while they enter the data) and display the appropriate message to them.
Since your app doesn't let people pick up where they left off, you could just dispose of the new, unsaved object when you load your passcode view. You must have a reference to the object they're editing-- so delete it, and move on. Just use the managed object context's deleteObject: method.
It would be a lot nicer if you could restore the previous state when they return to the app. Make your passcode view overlay the editing view maybe, instead of going back to the app's initial view. Then just hide the passcode view after the user enters their code, and the user continues where they were.
For what it's worth, changing properties from mandatory to optional should not require data migration. Not every change does. But that's not the best solution.
In practice, any value on a CoreData object that the user is responsible to populate should be able to be nil. Your business logic should enforce your rules, not CoreData. Only things such as keys or identifiers should be required to be populated to save.

Concurrent changes to NSManagedObjectContext - how and when to save

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.

Resources