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.
Related
I have two code paths that needs to execute on app launch:
1. When Crashlytics detects a report from the last run
2. When it is a clean launch, ie, no crash report was detected.
Crashlytics provides (and recommends) that this method be used to detect crashes:
- (void) crashlyticsDidDetectReportForLastExecution:(CLSReport *)report
but the documentation specifically says that the method is not called synchronously during initialization. So while I can use this for detecting case #1, I don't think it is possible to use the same method to detect case #2 without possibly introducing a race condition.
As far as I can tell, the current framework does not expose any method to check for the existence of a report, either in Crashlytics.h or CLSReport.h. If it did, I could check for the existence of a crash report before the framework initializes.
Suggestions?
Solution proposed by Mike (from Fabric)
Mike -- I'm used to assuming that delegate methods and callbacks cannot be assumed to happen synchronously, or on the same thread. You seem to be saying that I can/should make that assumption here, so that this (psdeudocode) would work:
(in AppDelegate)
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
self.HadCrash = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler(YES);
}];
}
(In AppDelegate didFinishLaunching)
crashlytics.delegate = self;
[crashlytics init]; // presumably if the delegate method IS going to be called, it will be called here.
if (!HadCrash)
{ // do "no crash" stuff here }
Mike from Fabric here, there are two methods that can be used to know about a crash that happened.
1) - (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report;
This method has the following restrictions:
It is not called synchronously during initialization
It does not give you the ability to prevent the report from being submitted
The report object itself is immutable
The most important benefits are that the ability to report crashes is not affected in any way.
2) - (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
This is called synchronously when the last execution of the app ended in a crash.
You can then take whatever action you want to take, but the report will not be sent unless the completionHandler is called with YES passed in. If NO is passed in then the call will be finished, but no report would be sent.
Here's a sample implementation from the docs:
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
// Use this opportunity to take synchronous action on a crash. See Crashlytics.h for
// details and implications.
// Maybe consult NSUserDefaults or show a UI prompt.
// But, make ABSOLUTELY SURE you invoke completionHandler, as the SDK
// will not submit the report until you do. You can do this from any
// thread, but that's optional. If you want, you can just call the
// completionHandler and return.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler(YES);
}];
}
I think that addresses the question, but let me know if I missed something.
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.
What I want to achieve is the following:
Complication(s) get updated in the background at intervals of 30
minutes
Complication(s) get updated whenever the watch app runs and
receives its own updated data
Complication(s) get updated whenever
the iOS app runs and the user changes a setting which affects the
watch data (such as changing location of weather observations, or
display units)
Items 1. and 2. seem to be straightforward, and nicely addressed here: What is the flow for updating complication data for Apple Watch?
However, for item 3, in the iOS app, I set up a WCSession instance and call transferCurrentComplicationUserInfo, sending the new settings as NSDictionary. In the watch extension, this invokes didReceiveUserInfo in WCSessionDelegate.
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo {
// Code here to apply the new settings
// ....
// Invoke a NSUSRLSession-based web query to get new data
[self queryForNewDataWithCompletionHandler:^(NCUpdateResult result) {
if (result == NCUpdateResultNewData) {
// Have new data from web to display
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in server.activeComplications) {
[server reloadTimelineForComplication:complication];
}
}
// Set date for next complication update to 30 mins from now
// ...
}];
}
The problem I am having is that watchOS is calling requestedUpdateDidBegin in a separate thread, shortly after it invoked didReceiveUserInfo and this starts executing BEFORE I have a chance to obtain updated data using the new settings in the newly received UserInfo dictionary from the app.
Consequently, the complications get updated twice in short succession - once by the WatchOS having called requestedUpdateDidBegin, which simply re-updates the complication with existing (stale) data, before I very soon after receive new data from the web and then have to update them again in my own code.
This seems unnecessary and a waste of resources, not to mention the limited budget of updates that Apple allows (supposedly 2 per hour).
Am I doing anything wrong here? How can I prevent watchOS2 from calling requestedUpdateDidBegin before I have had a chance to acquire new data from the web?
The purpose of transferCurrentComplicationUserInfo is to immediately pass current complication data to the extension. In your code, you are passing settings, however you are not including any weather data.
The issue you're seeing stems from trying to asynchronously fetch new data within the extension (which is returning before the data is available).
To handle this, you should fetch the current weather data on the phone based on the new settings, then pass (the new settings along with) the weather data in the current complication user info.
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo {
// Code here to apply the new settings for future updates
// ....
// Code here to update cache/backing store with current weather data just passed to us
// ....
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in server.activeComplications) {
[server reloadTimelineForComplication:complication];
}
}
This way, the complication server can immediately update the timeline using the current complication data you just transferred to the watch.
No stale data, no unnecessary second update.
One question and one issue:
I have the following code:
- (void) registerForLocalCalendarChanges
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(localCalendarStoreChanged) name:EKEventStoreChangedNotification object:store ];
}
- (void) localCalendarStoreChanged
{
// This gets call when an event in store changes
// you have to go through the calendar to look for changes
[self getCalendarEvents];
}
These methods are in a class/object called CalendarEventReporter which contains the method getCalendarEvents (in the callback).
Two things:
1) If the app is in the background the callback does not run. Is there a way to make it do that?
2) When I bring the app back into the foreground (after having changed the calendar on the device) the app crashes without any error message in the debug window or on the device. My guess is that the CalendarEventReporter object that contains the callback is being garbage-collected. Is that possible? Any other thoughts on what might be causing the crash? Or how to see any error messages?
1) In order for the app to run in the background you should be using one of the modes mentioned in the "Background Execution and Multitasking section here:
uses location services
records or plays audio
provides VOIP
services
background refresh
connection to external devices
like through BLE
If you are not using any of the above, it is not possible to get asynchronous events in the background.
2) In order to see the crash logs/call stack place an exception breakpoint or look into the "Device Logs" section here: Window->Organizer->Devices->"Device Name" on left->Device Logs on Xcode.
To answer your first question, take a look at https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
What I did to get code running in the background is to do something like
In the .h file
UIBackgroundTaskIdentifier backgroundUploadTask;
In the .m file
-(void) functionYouWantToRunInTheBackground
{
self.backgroundUploadTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
//code to do something
}
-(void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUploadTask];
self.backgroundUploadTask = UIBackgroundTaskInvalid;
}
The code above I pretty much learned from objective c - Proper use of beginBackgroundTaskWithExpirationHandler
As for your second question, you should set a breakpoint where code is supposed to run when you bring the app back to the foreground. No one can figure out why an app crashes if not given enough code or information.
The solution to the second part of the question was to raise the scope of the object containing the callback code. I raised it to the level of the containing ViewController. This seems to work. I still can't figure out how to raise the Notification (i.e. execute the call back) if the notification comes while the app is in the background/suspended. This prevented the object containing the callback from being cleaned up.
I need to save some data in my application, when the application terminates and even if it crashes. I know that applicationWillTerminate is called when application terminates but I am not sure which method is called when application crashes.
Can someone help me here?
Well you could add your own exception handler, to catch the error.
First you need to define the exception method:
void uncaughtExceptionHandler(NSException *exception) {
// You code here, you app will already be unload so you can only see what went wrong.
}
Then tell the app to use your exception handler:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
// The rest if you code ....
}
There is no way to make the app save data on crashing, since saving could be the reason for the crash!
No, you cannot get to know when the application crashes.