Detect deletion of Core Data - ios

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.

Related

CoreData refreshAllObjects() does not refresh all objects

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.

When to Persist Object Graph

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.

Store User Token, If Invalid token - present Login page,

Learning iOS Swift programming and like to know how to implement user login process ?
The backend-iOS mechanism is this :
User login with email and Password,
The Server returns user token and user id
In subsequent requests, user token and user id is sent to fetch data/work with the App.
I have doubt in iOS implementation.
Will be storing User token and User id in Core Data. Will there be
any slowness if I get the user token on every screen from Core
Data?
If the login token expires or is invalid on any screen, how to fall back to login page? Should I check the JSON output and have code "present login VC" on every screen? Any streamlined way to have abstract the code to a swift or cocoa touch file?
Actually, there are many approaches. It's all depends on you, how you will manage it. I can point you two examples, how I manage it by myself.
Using NSOperation.
There was an awesome session on WWDC 2015, about advanced NSOperations. Here it is.
Basically, you create a subclass of NSOperaton and make other operations depend on it. In your case, you will have operation of user login, and all other operations will depend on user login (of course only ones, who needs it). If it succeed, then operation will execute. In user login operation, you will check, if user already logged in, and you have a token, if not, present logging screen.
There is also awesome library called Operations, based on that WWDC talk. Here it is.
Using PromiseKit.
Here is another one, using PromiseKit. It is not really much difference from operations, but, in my opinion, a little simpler. You create Promise that ensures that you did login. It is very simple to make a chain of promises, so you promise a user login and chain anything else from it. If login succeed, chain of promises continues executing.
It's all based on another awesome library, PromiseKit. Here it is
It is very powerful and very simple to use, once you understand the thing. It is very well documented, and has a bunch of tutorials here.
There are many other approaches, so you can choose any of it, and combine one with other however you like.
Asking on your first question, you make it asynchronous, so it is not so much matter about slowness of CoreData as you make a web request.
1 - well, yes, a bit. But I doubt you will notice any lag or anything: CoreData is really fast and it won't take any significant amount of time to fetch just one object. Alternatively, your CoreData object that holds this data (let's call it User) can be a property of your subclass of UIApplicationDelegate (let's call it MyAppDelegate). You will fetch this User in
- application:didFinishLaunchingWithOptions:, and update it on login/logout/expire/etc. In such way you can access it from everywhere, and you don't need to fetch it from CoreData anytime you need it.
Also, instead of CoreData you can use iOS Keychain. (Here is tutorial that might help with this).
2 - again, you can use MyAppDelegate. For example you can add method to it, that will save current navigation state, and change root controller of UIWindow to your LoginViewController. Or it will present LoginViewController on top of current controller. After successful login you will return navigation stack to previous state.
Also you can move this code to some kind of NavigationController - class that will handle this situation. It will have something like
func checkToken(Dictionary response, UIViewController currentController) -> Bool. If token is valid, it just returns true, and if not, it returns false and handles navigation to LoginViewController, and after login - back to currentController. This NavigationController can be property of MyAppDelegate, it can be singleton, created every time you need it, or you can pass it along to every UIViewController that you show.

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