when minimizing the application, all is well, but when I try to deploy the boom going on, this error
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
-(void) applicationDidEnterBackground:(UIApplication *)application
{
NSDate *alertTime = [[NSDate date] dateByAddingTimeInterval:5];
UIApplication* app = [[UIApplication sharedApplication] init];
UILocalNotification* notifyAlarm = [[UILocalNotification alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:kAllNews]];
NSError *error = nil;
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
NSError *jsonParsingError = nil;
sortArray = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError];
if (notifyAlarm)
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"news"])
{
newsNew = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"news"]];
}
if (newsNew.count > sortArray.count) {
notifyAlarm.fireDate = alertTime;
notifyAlarm.timeZone = [NSTimeZone defaultTimeZone];
notifyAlarm.repeatInterval = 0;
notifyAlarm.soundName = #"Glass.aiff";
notifyAlarm.alertAction = #"Gipoteza";
notifyAlarm.alertBody = #"Добавлена новая новость";
[app scheduleLocalNotification:notifyAlarm];
}
}
}
A couple of suggestions:
First; did you try putting your code in applicationWillResignActive instead?
Why are you getting a reference to UIApplication in your code? You already have that as a parameter to the method...
Try putting breakpoints in your code to see exactly where this error occurs. I would guess it happens on the last line: [app scheduleLocalNotification:notifyAlarm], and that it happens because your notifyAlarm object for some reason is nil. Use your debugger to step through the code to see if that object does indeed get set.
Also; be aware that your code has a maximum of 5 seconds from deactivation until it becomes suspended. When suspended; no code can run. If, for example, your NSURLConnection request for some reason takes long to respond, your app will be suspended before the code is finished.
Related
I am having an unexpected issue with a crash on my app which I am specifically struggling to debug because it occurs in background at a time determined by the iOS system. I have some capitalised comments to the code which show where the issue is being back traced to. I hope this is clear.
I believe it has to do with object deallocation.
I have tried using the __block before initialising the object but
this has not helped.
I have also tried dispatching the lines of code
in error to the main queue but that has not helped.
The actual crash is listed as AppName: __66-[BackgroundUpdateController initiateBackgroundHealthkitObservers]_block_invoke_5 + 160
I apologise if some of the code does not fit standard formatting and conventions. I am self taught from a variety of places and so do not have proper experience with code format.
Many Thanks
#import "BackgroundUpdateController.h"
NSUserDefaults *backgroundDefaults;
#implementation BackgroundUpdateController
-(id)init{
backgroundDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.HeartAnalyzer"];
return [super init];
}
-(void)initiateBackgroundHealthkitObservers{
// Check we should be running here
if(([backgroundDefaults integerForKey:#"sleepAnalysisEnabled"] != 1) || (![backgroundDefaults boolForKey:#"AutomaticSleepAdd"])) return;
// Initiate some variables, Use __block to ensure the backgroundHealthStore object does not get deallocated
__block HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
HKQuantityType *activeEnergy = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
// Enable background delivery of active energy data from HealthKit
[backgroundHealthStore enableBackgroundDeliveryForType:activeEnergy frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
}];
// Now setup an HKOberverQuery which triggers hourly if there are new active energy data points in HealthKit
HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:activeEnergy predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive){// Only run when app is not in foreground
// Load some more variables with checks to ensure they are valid objects
NSDate *previousSavedDate = [backgroundDefaults objectForKey:#"DateBackgroundSleepLastSaved"];
if(previousSavedDate == nil) previousSavedDate = [NSDate distantPast];
NSDate *lastSleepCheck = [backgroundDefaults objectForKey:#"LastSleepCheck"];
if(lastSleepCheck == nil) lastSleepCheck = [NSDate distantPast];
// If the last save date was long enough ago and the last sleep check was long enough ago, proceed
if(([previousSavedDate timeIntervalSinceNow] < -(3600*18)) && ([lastSleepCheck timeIntervalSinceNow] < -(3600*2))){
[backgroundDefaults setObject:[NSDate date] forKey:#"LastSleepCheck"];
[backgroundDefaults setBool:NO forKey:#"BackgroundSleepFound"];
SleepTimesCalculator *sleepClass = [[SleepTimesCalculator alloc] init];
[sleepClass calculateSleepTimes:^{
NSLog(#"Background sleep time calculations complete");
if([backgroundDefaults boolForKey:#"BackgroundSleepFound"]){// Only continue is a sleep time was found
__block NSMutableArray *savedSleepObjects = [backgroundDefaults valueForKey:#"SleepTimesDataBase"];
if(savedSleepObjects.count > 0){
__block NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0]; // THE __BLOCK USED TO PREVENT THE OBJECT BEING DEALLOCATED, STILL SEEMS TO BE BASED ON THE CRASH
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedSleepTime"]integerValue]];// Get the sleep time start date object
NSDate *sleepEnd = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedWakeTime"]integerValue]];
NSInteger sleepSavedToHealth = [[sleepObject valueForKey:#"SavedToHealth"] integerValue];// Check its not already been saved by some other element of the app
if(sleepSavedToHealth != 1){
HKCategorySample *sleepSample = [HKCategorySample categorySampleWithType:[HKCategoryType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis] value:1 startDate:sleepStart endDate:sleepEnd];// Generate sleep object for HealthKit
[backgroundHealthStore saveObject:sleepSample withCompletion:^(BOOL success, NSError *error) {
if (!success) NSLog(#"Uncommon Error! saveObject:sleepSample");
else{
dispatch_async(dispatch_get_main_queue(), ^{// DISPATCH TO MAIN QUEUE AN ATTEMPTED FIX FOR CRASH
sleepObject = [savedSleepObjects objectAtIndex:0];// Choose the most recent sleep time to save
[sleepObject setValue:[NSNumber numberWithInteger:1] forKey:#"SavedToHealth"];// THIS IS WHERE THE 'Last Exception Backtrace (0)' ENDS UP
[savedSleepObjects replaceObjectAtIndex:0 withObject:sleepObject];// Replace the object which now has the 'Saved' tag
[backgroundDefaults setObject:[NSDate date] forKey:#"DateBackgroundSleepLastSaved"];// Save the data of the last time we reached this point
[backgroundDefaults setObject:savedSleepObjects forKey:#"SleepTimesDataBase"];// Save the sleep times back to the database
});
}
}];
}
completionHandler();// Call the completion handler as we've been throught the sleepObjects array
}
else completionHandler();// Call the completion handler anyway
}
else completionHandler();// Call the completion handler anyway
}];
}
else completionHandler();
}
}];
[backgroundHealthStore executeQuery:query];// Execute the HealthKit healthstore query
}
#end
Prefixing __block does not guarantees existence of an object for #"CalculatedSleepTime" key in sleepObject
I think you have misinterpreted how __block works. This will be a great guide.
On a quick overview of the code, it seems like [sleepObject valueForKey:#"CalculatedSleepTime"] is returning nil & without a nullability check you are trying to extract the integerValue
So, consider:
NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0];
id calculatedSleepTime = [sleepObject valueForKey:#"CalculatedSleepTime"];
if(calculatedSleepTime){
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[calculatedSleepTime integerValue]];
}
And it looks like you also don't require the __block prefix in HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
Since 2 yrs I have been trying different ways to find the solution of app crash while click back button.
My application scenario:
In a tableview contoller I have to load list of users, On view did load I call getData(Asyncronous download) API method to load data. At the time of data download, If user press back button my application gets crash due to null value objects. That says all of my variable memory deallocated.
To overcome this problem, I used some loading indicator which lock UIScreen untill data download.
Questions:
Is there any alternatives to prevent crash, UIScreen Lock
Other applications use Activity Indicator in Menu bar without UIScreen Lock. How they are doing?
Need help to recover this issue
Here is my sample code to download data :
Below code doesnt crash app. But it download data even I cancel operations on dealloc
viewDidLoad:
ShowNetworkActivityIndicator();
_processQueue = [[NSOperationQueue alloc] init];
_processQueue.maxConcurrentOperationCount = 4;
_processQueue.name = #"Events Processing";
[self loadData];
loadData:
-(void)loadData
{
[_processQueue addOperationWithBlock: ^ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[[NSURL alloc] initWithString:#"https://restcountries.eu/rest/v1/all"]];
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSString *data=[[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
NSDictionary *search = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
_countryListArray=[search mutableCopy];
[self.tableViewSample reloadData];
HideNetworkActivityIndicator();
}];
}];
}
I tried cancelAllOperations in dealloc:
[_processQueue setSuspended:YES];
[_processQueue cancelAllOperations];
Can you try inserting the reload data is dispatch_async(dispatch_get_main(),void (^){}); callback , main thread , I think the reload happening in the background thread is crashing the app.
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ { _countryListArray=[search mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{
[self.tableViewSample reloadData];
HideNetworkActivityIndicator();
});}];
I have an NSTimer in an iOS that is polling a database server at every 10th seconds for a data row in a table based upon a certain data ID, that has been sent as an argument via a PHP-script. If the data ID matches the data ID of the row that have been inserted by an external source then the app will show an alert box containing the information from the data row and the NSTimer will stop to tick.
But this only works while the app is running in the foreground and I want to show the information message as a local notification so that even though the user has exited from the app, it will still poll the server when the app is running in the background as well.
I have read on many sites that the Local Notification service and background fetch is the right kind of solution but I don't know how to set it up really, it is very confusing.
Because I have seen many examples where Local Notification is used to send reminders at certain dates on the calendar and trigger alarms at certain times and not so much about polling to a server.
How do you set up a Local Notification that will poll to a server at the interval of 10 seconds and then cancel it as soon as it receives right kind of information that it will display at last?
Here is how I have done so far:
...
NSTimer *confirmedTimer;
int orderId = 1;
...
-(IBAction) sendButton: (id) sender {
confirmedTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(confirmedTick) userInfo:nil repeats:YES];
}
-(void)confirmedTick {
NSString *paramsConfirmed = [NSString stringWithFormat:#"order_id=%d", orderId];
NSData *postDataConfirmed = [paramsConfirmed dataUsingEncoding:NSUTF8StringEncoding];
NSURL *urlConfirmed = [NSURL URLWithString:#"http://www.serverexample.com/confirmed.php"];
NSMutableURLRequest *requestConfirmed = [NSMutableURLRequest requestWithURL:urlConfirmed];
[requestConfirmed setHTTPMethod:#"POST"];
[requestConfirmed addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[requestConfirmed setHTTPBody:postDataConfirmed];
[requestConfirmed setValue:[NSString stringWithFormat:#"%i", postDataConfirmed.length] forHTTPHeaderField:#"Content-Length"];
NSURLResponse *responseConfirmed;
NSError *errorConfirmed = nil;
NSData *receivedDataConfirmed = [NSURLConnection sendSynchronousRequest:requestConfirmed
returningResponse:&responseConfirmed
error:&errorConfirmed];
if(errorConfirmed) {
if([responseConfirmed isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponseConfirmed = (NSHTTPURLResponse *)responseConfirmed;
return;
}
return;
}
NSString *responseStringConfirmed = [[NSString alloc] initWithData:receivedDataConfirmed
encoding:NSUTF8StringEncoding];
if ([responseStringConfirmed isEqualToString:#"true"]) {
return;
}
NSDictionary *jsonObjectConfirmed = [responseStringConfirmed objectFromJSONString];
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:receivedDataConfirmed options:0 error:nil];
NSArray *confirmedArray = [jsonDictionary objectForKey:#"confirmed_table"];
if([confirmedArray count] > 0)
{
[confirmedTimer invalidate];
NSString *confirmedMessage = #"";
for(NSDictionary *confirmed in confirmedArray)
{
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"confirmed_id: %#\n", [NSNumber numberWithInt:[[confirmed objectForKey:#"confirmed_id"] intValue]]]];
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"order_id: %#\n", [NSNumber numberWithInt:[[confirmed objectForKey:#"order_id"] intValue]]]];
confirmedMessage = [confirmedMessage stringByAppendingString:[NSString stringWithFormat:#"Information: %#", [confirmed objectForKey:#"information"]]];
}
UIAlertView *confirmedAlert = [[UIAlertView alloc]
initWithTitle:#"Confirmation"
message:confirmedMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[confirmedAlert show];
[confirmedAlert release];
}
}
You have it slightly backwards. The local notification doesn't check the server. Rather you implement background fetch and then post a local notification if the background fetch detects the relevant data. There is a good tutorial on background fetch here.
Note that background fetch won't execute every 10 seconds
I have an issue that my NSTimer doesn't work when the app is working in backgroud.
on the simulator it is work, on the device it is not.
my code on the AppDelegate.m:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"enter background");
notiTimer = [NSTimer scheduledTimerWithTimeInterval:4 target:self selector:#selector(checkNotification) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:notiTimer forMode:NSDefaultRunLoopMode];
}
and the method I am trying to run in loop:
-(void)checkNotification {
NSLog(#"running loop in bg");
userdetails =[NSUserDefaults standardUserDefaults];
userid = [userdetails objectForKey:#"userid"];
username = [userdetails objectForKey:#"username"];
if (userid != NULL && username != NULL) {
notifString = [NSString stringWithFormat:#"userid=%#", userid];
notifData = [NSData dataWithBytes: [notifString UTF8String] length: [notifString length]];
urlreq = [[NSMutableURLRequest alloc] initWithURL:notifUrl];
[urlreq setHTTPMethod: #"POST"];
[urlreq setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"content-type"];
[urlreq setHTTPBody: notifData];
sendreq = [NSURLConnection sendSynchronousRequest:urlreq returningResponse:nil error:nil];
response =[[NSString alloc] initWithBytes:[sendreq bytes] length:[sendreq length] encoding:NSUTF8StringEncoding];
responsTrimmed = [response stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
splitResponse = [responsTrimmed componentsSeparatedByString: #","];
if ([splitResponse[0] isEqualToString:#"1"]) {
NSLog(#"%#",splitResponse[1]);
}
else {
NSLog(#"err");
}
}
}
now once at the app enter to background I get at the debugger the NSLog:
NSLog(#"enter background");
but the run loop doesn't work and the method doesn't call in loop and I don't get NSLog
NSLog(#"running loop in bg");
on the debugger, any idea ?
When your application goes in background, it is "frozen" by the OS, so no task can be executed in the normal way.
The application itself should declare how it handles background task, but you are limited to one of this situation:
you need a little amount of time (usually less then 10 minute): you can simply ask for it
your app is downloading something: you can ask the system to continue the download for you
a little set of task can be executed in background, but you have to specify it in your project
So in order to execute some code, you have to start a background task at quit time.
Here is the Apple documentation for Background Execution, hope it can help you.
-(void) parseXML
{
[self performSelector:#selector(parseXML) withObject:self afterDelay:55.0 ];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://apikeygoeshere.com/data.xml"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *xmlString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSDictionary *xml = [NSDictionary dictionaryWithXMLString:xmlString];
NSMutableArray *items = [xml objectForKey:#"TeamLeagueStanding"];
NSMutableArray *newTeamObjectArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in items) {
TeamObject *myTeams = [TeamObject teamFromXMLDictionary:dict];
[newTeamObjectArray addObject:myTeams];
}
NSNull *nullValue = [NSNull null];
NSNull *nullValue2 = [NSNull null];
[newTeamObjectArray insertObject:nullValue atIndex:0];
[newTeamObjectArray insertObject:nullValue2 atIndex:1];
NSLog(#"standingsdataaaaa %#", newTeamObjectArray);
}
I want to add a unbutton to my storyboard so the user can refresh the data whenever he wants, but i don't him to be able to do this more than once per hour,
Can anyone help me? Thank you.
Just in the action method or wherever you call to get the XML
setEnabled: NO and set an NSTimer to fire nod a date that is 3600 seconds from now.
When it fires, setEnabled:YES
It might be nice to create a visual indicator to the user like a counter.
EDIT: In order to account for the fact that you still want to run the parseXML method every 55 seconds with or without the button press, I'm changing my answer by putting the conditional in the IBAction method triggered by the button press instead of putting the conditional in parseXML:
Declare an NSTimer as a class variable. For example, at the top of your .m directly after your #synthesizes, declare an NSTimer:
NSTimer *parseTimer;
Then in the IBAction method triggered by the button press, only call parseXML if the timer is nil; and if it is in fact nil and the parseXML method is going to run, initiate the timer so it doesn't run again for another hour:
- (IBAction)buttonPressed:(sender)id {
// If the parseTimer is active, do call parseXML.
// (And perhaps fire an alert here)
if (parseTimer != nil) return;
// Otherwise initialize the timer so that it calls the the method which
// will deactivate it in 60*60 seconds, i.e. one hour
parseTimer = [NSTimer scheduledTimerWithTimeInterval:60*60 target:self selector:#selector(reactivateButton) userInfo:nil repeats:YES];
[self parseXML];
}
The deactivateParseTimer method should deactivate the timer and set it to nil so that parseXML may run again:
- (void)deactivateParseTimer {
[parseTimer invalidate];
parseTimer = nil;
}