Avoiding data loss is a critical issue in my application. So I need to save early and often to ensure minimal loss and immediate notification if anything goes wrong.
Unfortunately this results in a lot of if (![context save:&error]) { handling all over the place. To clean this up in a more centralised way I thought to implement an NSManagedObjectContext category with a method such as this:
- (BOOL)saveNotify:(NSError **)error
{
NSError *saveError;
if (![self save:&saveError]) {
NSDictionary *userInfo = #{#"error" : saveError};
[NSNotificationCenter.defaultCenter postNotificationName:NSManagedObjectContextSaveErrorNotification object:self userInfo:userInfo];
if (error != NULL) *error = saveError;
return NO;
}
return YES;
}
Now I can listen for the notification in a more general way and throw up an alert to the user if the save has failed for some reason.
Is this a good approach or is there a better way to deal with this?
A secondary issue is what to actually do when this does occur. Asking the user to exit the app and restart is a bit convoluted these days as they need to close the app through the task switcher.
That'll work for notifying users of save errors. But there are a couple of things to keep in mind:
The message in saveError may not be comprehensible to users. You probably shouldn't present it. Instead, look at the error code and domain and try to present something that makes more sense.
You'll need to try and work out potential errors and likely solutions. Telling users to force-quit the app is unlikely to help if, for example, the persistent store has become corrupted or if the device is out of space. Most save errors will be things that come up during development, if you're testing thoroughly. Things like validation failures or missing required values.
Related
SITUATION I am trying to figure out the best practices for error handling with the parse.com iOS SDK. I have read the parse docs and they do a great job of documenting how to check for connectivity to parse and if objects can be found, but my question would be what do I do then?
EXAMPLE
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if ([error code] == kPFErrorConnectionFailed) {
//COULD NOT REACH PARSE
//SO WHAT NOW?
}
else {
//EVERYTHINGS COOL
}
}];
SO WHAT NOW? Am I supposed to have this on an NSTimer and fire this off again in 5 minutes to see if we can reach parse then?
If saving objects is as important as it seems to be for your case, then this could be a solution, instead of using an NSTimer:
In the SO WHAT NOW? block, just call the method that saves this object recursively. If you ever get an error other than ConnectionFailed you could handle that appropriately, but if you're just worried about saving this even if the first attempt fails, this could be a way.
I have got requirement in one of my projects where user is asking to save data locally in case of sudden app crashes. Its form based app so user fill lots of data in form and if app crashes suddenly then user lost all data entered.
App is using Core Data to save data locally.
There is a point where we save entered data in core data but not during every second user fills data.
Also if app crashes core data also vanishes.
In fact, if your app crash, the callback :
- (void)applicationWillTerminate:(UIApplication *)application
will not be called. This one is only called if for some reasons the system need to shutdown your app (usually because resources are rare or because your background job is still doing work after the maximum time allowed) or if you are on < iOs 4. You don't have any way to know when your app will crash (but when you relaunch the app, you can know if it had crashed).
So for your particular case you have two solutions :
Use either a NSTimer with a quick firing rate, or call a fonction each time you edit a field and update the core-data context then save it on disk.
NSError *error = nil;
[managedObjectContext save:&error]
Did you set a persistentStoreCoordinator on your context ? If no, core-data will never persist your data on disk.
Crashes don't appear out of nowhere, find the place(s) where crashes might happen and fix it or if you can't, use a try-catch to keep your app running (but please, try not to do that...).
Hope this help
You can implement a HandleException to catch all exceptions that crash your application.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//for uncaughted exceptions
NSSetUncaughtExceptionHandler(&HandleException);
struct sigaction signalAction;
memset(&signalAction, 0, sizeof(signalAction));
signalAction.sa_handler = &HandleSignal;
sigaction(SIGABRT, &signalAction, NULL);
sigaction(SIGILL, &signalAction, NULL);
sigaction(SIGBUS, &signalAction, NULL);
//and more if you need to handle more signals
//another setup ...
return YES
}
#pragma mark crash management
void HandleException(NSException *exception) {
NSLog(#"[FALTAL] App crashing with exception: %#", exception);
//try to save your DB here.
}
void HandleSignal(int signal) {
//try to save your DB here.
}
#pragma -
However, I don't know about how many time you will have before application exits. But I suppose that you will have enough time to do the DB-backup task.
In particular case you can should use, try catch block, (but not) everywhere.
try {
//write your code
}catch(NSException *e){
//See what's the error
}finally{
//Save your context
}
This it the best solution in my thinking. However you can create a NSTimer which executes a method at some reasonable seconds where you can hold and save your context.
You can also save your context in AppDelegate's method like (if you're targeting iOS 4.0 and above and if your app was exit by iOS it self for some reason),
- (void)applicationWillTerminate:(UIApplication *)application{};
below method will always call when your app goes in background,
- (void)applicationDidEnterBackground:(UIApplication *)application{};
Use
(void)applicationWillTerminate:(UIApplication *)application
delegate method of AppDelgate to save your data in core data.
I am using the following:
Firebase *fb =[[Firebase alloc] initWithUrl:url];
[fb setValue:d withCompletionBlock:^(NSError *error, Firebase *ref) {
if (error) {
// bad news
} else {
}
}];
This seems to work great IF you have a connection, if not it seems the callback is never called. If that is the case do I then need to wrap this whole thing in a connectedRef? Seems like alot of extra work when I would guess the completion block would just fail with an error status of not online.
Anyone else having this issue?
The idea behind Firebase is it synchronizes data for you. It's more than just a simple request / response system. So if you do a setValue while offline, Firebase will hold onto that data until you are online, and then it'll do the setValue at that time (and then the completion block will be called).
So the behavior you're seeing is expected. If you only want to do the setValue if you're online, then yes, you'll need to use a .info/connected observer. But you could still run into issues if for instance you go offline at the moment you try to do the setValue or something along those lines. In general it's better to just do the setValue and let Firebase take care of it for you.
I have an object that I load up data from Core Data. I then modify the object with user inputs/choices.
My first though was to override the setter methods for the properties:
-(void)setType:(NSString *)type {
NSLog(#"setType fired | newType: %#", type);
_type = type;
appDelegate *appDelegate = (appDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:DEFAULTS_DB];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects.count == 1) {
Defaults *defaults = [fetchedObjects objectAtIndex:0];
defaults.sceneType = type;
}
if (![context save:&error]) {
NSLog(#"Error in saving first run defaults | error: %#", error);
}
else {
NSLog(#"new type was saved to core data");
}
}
My other thought was to update the Core Data when applicationWillResignActive: fires (but that method only gets a few seconds to run before the app is frozen) and when the user logs out.
The app i'm creating is one that the user would start up, set up what he wants, then puts it down 10-60min until he uses it again so i'm concerned with my app being killed while inactive and loosing the data.
Is updating core data in the setter method a good way to handle updates or is it a really bad idea (too resource intensive, too slow)?
It's not a typical usage pattern. Typical would be to fetch your object before displaying its data and hold it. Then when the user changes something, update object properties and save the context.
It sort of depends on how much amount of data you are setting and retrieving with each call to your customer setters. I'd try what you are doing now (updating core data in setters) but test it out on an old device, say an iPad 1, and see how it performs. After playing it out for a while, you can decide whether you want to update core data with every setter or not.
Now, if you don't see any performance difference at all, I wouldn't worry about it. You're good. You are having the comfort of being able to save the most updated actions from the user by committing your data in setters. However, there might come a point when you are saving more data and/or large files/images and the earlier devices start to see a difference in performance.
In order to be prepared for such a situation in advance, I'd suggest having a buffer (that mirrors the structure of your entity) in your program that gets written to core data every so often - may be when the app goes into a state of inactivity or if the app goes into background or for every fixed time period. It's imperative the buffer gets updated in the setters. The possible hiccup with your idea of writing code in applicationWillResignActive: stems from how prepared you are at handling crashes. Crashes can happen any time for any reason, sometimes maybe not because of your own bad code. And this is one strong reason why having buffers is a good idea. You either lose one chunk of small data during a crash (worst case) or you achieve performance + consistency (best case).
I have researched a ton of posts regarding Core Data on background threads, and I feel like I understand (on paper) what needs to be going on. I guess we'll see. I am working on migrating an existing OS X app to Core Data, and am having issues making new instances of my NSManagedObject on an async thread.
Here is a sample of the code I am running right after I have moved onto a background thread:
NSLog(#"JSON 1");
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
asset = (MTAssetInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"Info" inManagedObjectContext:context];
NSLog(#"JSON 2");
The result is that the first log message (#"JSON 1") gets called 31 times, and the second one (#"JSON 2") is never called. The object isn't being made and returned correctly.
The model for this Info entity is quite complex with a few transformable attributes that may or may not be setup correctly. The weird thing is that similar code run on the main thread and the main MOC works great. No issues.
EDIT - Some more context
The async call originates from here:
for (NSNumber *sectionID in sectionsToShow) {
dispatch_group_async(group, queue, ^{
MTAssetInfo *asset = [self assetWithRefID:[sectionID unsignedIntegerValue]];
if (asset != nil) {
[sectionsLock lock];
[sectionsTemp addObject:asset];
[sectionsLock unlock];
}
});
}
The assetWithRefID method never returns with an object because of the other code snippet. It never successfully pulls an NSManagedObject out of the context on the background thread.
You are going to have to provide more information to get real help, but I bet your problem is an error happening in the NSManagedDocument background thread.
I'd register a NSNotificationCenter for ALL messages (name:nil object:nil) and just print them out. I bet you see a status change or error message in there that is failing.
You might want to try a #try/#catch block around it just to see if exceptions are being thrown.
Maybe it will give you more to go on.
One other suggestion... Swizzling isn't necessarily the right tool for production stuff, but it's almost unbeatable for debugging. I have method-swizled several entire classes, so that it sends a detailed NSNotification before/after each invocation.
It has saved me tons of time, and helped me track down some wicked bugs. Now, when something is going on in CoreData, I take out my set of classes, link them in, and see all the detail I want.
I know that does not exactly answer you question, but hopefully it will put you on the track so you can provide some more information and get it all fixed.
If that's too much for you, create a subclass and instantiate that, with a similar method for calling super. You can get a real idea of the entire flow pretty easily.