So, I am trying to create a small project that tells user how many followers he has.
Currently I'm using Instagram Kit, which uses asynchronous blocks.
My aim is to make program update it's data in the background fetch and change the badge value next to the icon to the number of followers. I had some troubles with the background fetch cause the block that gets the information from instagram completes after the fetch, so no data is retrieved.
I read about semaphores and decided to use them. After i added some code to the method that is called in background fetch, it does the following:
the fetch starts (I get the "We begin" NSLog), then the main thread is freezed by semaphore. It should be resumed by the completition of the "withSuccess"/"failure" part of block. However it gets sucked. And nothing else happens. I am puzzled.
What should I do?
(below is the method that is called during the fetch, I store the instagram-token locally, so no need to request it every time)
-(IBAction) backgroundActions {
NSLog(#"We begin");
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *user) {
NSLog(#"%d",user.followedByCount);
[UIApplication sharedApplication].applicationIconBadgeNumber = user.followedByCount;
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
NSDate *now = [NSDate date];
localNotification.fireDate = now;
localNotification.alertBody = [NSString stringWithFormat:#"You have %d followers.", user.followedByCount];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = user.followedByCount;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
dispatch_semaphore_signal(sem);
} failure:^(NSError *error) {
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
What you've done is make the asynchronous getSelfUserDetailsWithSuccess become synchronous. That is why your main thread is freezing.
Otherwise the code looks fine. What's wrong with removing the semaphore and letting the update happen asynchronously?
Related
I need to run some code when the user enters a background state. The default time I was getting for when I entered the background on iOS 9 was 10 seconds. I needed a bit more than that, so I found that this code will extend the time to 3 minutes:
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
NSLog(#"Attempting to extend background running time");
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"DummyTask" expirationHandler:^{
NSLog(#"Background task expired by iOS");
if (self_terminate) {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Background task started");
while (true) {
NSLog(#"background time remaining: %8.2f", [UIApplication sharedApplication].backgroundTimeRemaining);
[NSThread sleepForTimeInterval:1];
}
});
}
However, my task doesn't need all of this extra time, and I would like to conserve as much battery as possible. Is there any way to use this or similar code to get 1 minute of background time, or some other value between 10 and 180 seconds?
You should call endBackgroundTask: once you are done with your background processing. If you are done before the 3 minute time allotted to you, that should end your background processing early and let iOS suspend you. I haven't tested it to verify, but that's what the docs suggest.
It may seem that this question was asked several times, but I'm facing a weird problem.
I have server configured to send push notification with content-available = 1 flag.
I have configured my app to work in background Background Modes on for Location Update, Background fetch and Remote Notifications.
Also I have implemented all necessary code to receive push notifications in background and to start background task.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
__block UIBackgroundTaskIdentifier bg_task = background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: bg_task];
bg_task = UIBackgroundTaskInvalid;
}];
//### background task starts
[self updateLocationToServer];
//#### background task ends
completionHandler(UIBackgroundFetchResultNewData);
}
- (void)updateLocationToServer{
[locationManager updateLocationWithCompletionHandler:^(CLLocation *location, NSError *error, BOOL locationServicesDisabled) {
if (error)
{
// Handle error here
if (locationServicesDisabled) {
// Location services are disabled, you can ask the user to enable them for example
}
}
else
{
// Do whatever you want with the current user's location
NSString *deviceID = [userDefs objectForKey:#"deviceID"];
isConnected = [[userDefs objectForKey:#"connected"] boolValue];
if (isConnected) {
if (deviceID) {
[self sendLocation:deviceID];
}
}
localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = [NSString stringWithFormat:#"Lat: %# Long:%#",[NSNumber numberWithFloat:location.coordinate.latitude],[NSNumber numberWithFloat:location.coordinate.longitude]];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
NSLog(#"Lat: %# Long:%#",[NSNumber numberWithFloat:location.coordinate.latitude],[NSNumber numberWithFloat:location.coordinate.longitude]);
//Clean up code. Tell the system that we are done.
[[UIApplication sharedApplication] endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;}}];}
EDIT: Added code where I end background task. background_task variable is global.
The app receives push in background normally until it goes to suspended mode. The problem is that, after background task ends, and the app goes to suspended mode it does not run the code again when it receives push notification but didReceiveRemoteNotification: fetchCompletionHandler: does not get called. But when I open the app and exit with home button it will work again within "that" 3 minutes until it goes to suspended mode.
I would like to save data when app goes in background. I am doing cancelling NSOperation and saving data in applicationDidEnterBackground. But it does not complete execution.
How can I complete this before my app goes in background?
Code :
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//FUNCTION_START
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[self performSelectorOnMainThread:#selector(dispatchStateNotification:)
withObject:[NSNumber numberWithInt:666]
waitUntilDone:YES ];
// Write to core data
GWSCoreDataController *dataController = [GWSCoreDataController sharedManager];
NSError *error;
if (![dataController.managedObjectContext save:&error]) {
NSLog(#"Error while saving data to Core Data: %#", [error localizedDescription]);
}
// FUNCTION_END
}
-(void)dispatchStateNotification:(NSNumber *)value {
[[NSNotificationCenter defaultCenter] postNotificationName:APPLICATION_ENTERED_BACKGROUND_NOTIFICATION object:value];
}
You can start a background task, and do your cleanup stuff
- (void)applicationDidEnterBackground
{
UIBackgroundTaskIdentifier bgTaskId =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTaskId];
}];
// Start cleanup
.......
[[UIApplication sharedApplication] endBackgroundTask: bgTaskId];
}
I had the same issue and had to put the save call in applicationWillResignActive instead - didEnterBackground just doesn't seem to have the complete CoreData to save with...
I have solved this problem for my requirement like below. Added one flag and run while loop till this flag will not become false. As soon as my task will get complete or app comes in foreground I have marked this flag as false.
// start the task asynchronously which is written into the block on new thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
//this loop runs continuously while flag is YES.
while(appDidEnterBackground){
sleep(1);
}//end of while
//ends the background task.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});//end of dispatch queue
I'm working on a VoIP-based IOS App.
There are two ways to play some sound to notify the user when a call come in:
Send UILocalNotification with sound. The sound will last for at most 30 seconds;
Play a local Music Asset in setKeepAliveTimeout:handler: function. But System just gives me 10 seconds to do the operation.
Is there any way to play the sound forever like the native Phone app?
I'm afraid #Dan2552 is correct.
Here's what Apple states:
Sounds that last longer than 30 seconds are not supported. If you
specify a file with a sound that plays over 30 seconds, the default
sound is played instead.
EDIT:
Playing an audio file for more than 30 seconds (or forever, for that sake) can be achieved by using AVAudioPlayer from AVFoundation
#import AVFoundation; // module -> no need to link the framework
// #import <AVFoundation/AVFoundation.h> // old style
- (void)playAudio
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"mp3"];
NSError *error = nil;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (!error) {
player.numberOfLoops = -1; // infinite loop
[player play];
} else {
NSLog(#"Audio player init error: %#", error.localizedDescription);
}
}
Then instead of setting the soundName property of the local notification you have to call this method on the main thread:
[self performSelectorOnMainThread:#selector(playAudio) withObject:nil waitUntilDone:NO];
The problem with Islam Q.'s answer is that AVAudioPlayer won't work for a notification since the app will be muted when you're not using the app. Only a UILocalNotification can let your app produce sound when it's not active. The 30 second limit is real. It should be done by repeating the notification with another 30 seconds notification. I don't know how long other VOIP applications will keep ringing but even with a traditional phone there is limit usually, something like a minute.
My strategy would be:
Send initial notification with 29 seconds alarm that somebody tries
to contact
Send second notification with a different 29 seconds alarm after 40 seconds that you are about to miss a call
Send a third notification that you missed a call after 100 seconds
This would allow the user 1:40 of time to respond to a call.
Your code would look something like this:
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"FIRST_CALL", #"%# is calling you!");
notification.soundName = #"normal_ring.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(40 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (!phone.isRinging)
{
// Somebody picked it up already in the mean time
return;
}
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"HURRY_CALL", #"Hurry, you are about to miss a call from %#!");
notification.soundName = #"hurry_ring.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (!phone.isRinging)
{
// Somebody picked it up already in the mean time
return;
}
UILocalNotification* notification = [[UILocalNotification alloc] init];
notification.alertBody = NSLocalizedString(#"MISSED_CALL", #"You've just missed a call from %#");
notification.soundName = #"missed_call.mp3";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
});
If you use the code, keep in mind you first need to make sure the app is in the background so the UILocalNotification would be the only option you have. Use a normal AVAudioPlayer when it's in the foreground, much more flexible. Second thing is that the app becomes foreground when the user responds to the call so it should mute the alert and let the AVAudioPlayer take over.
You want more or less a smooth transition between the alert and the in app sound to have a fully polished experience for your users so the best thing to do would be to measure the time between rings and let AVAudioPlayer start in exact the right time at the start of a new loop.
Your second problem is keeping the app alive in the background. This simple code will simply keep on polling the app every now and then to keep it alive. It comes straight from a working application that does just this, this code polls if a timer is still running and sleeps for five seconds when it is:
UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[timerExecuted timerUpdate];
while (!timerExecuted.isStopped)
{
[NSThread sleepForTimeInterval:5];
}
[application endBackgroundTask:backgroundTask];
});
I'm building an app with Region monitoring. It works fine in foreground but once the app is sent in background, it's not behaving as expected: it does call didEnter and didExit but as soon as it starts executing the callbacks it stops. In these callback methods i need to poll a server and persist didExitRegion and/or didEnterRegion status. As soon as I put the app in foreground again, any queued request starts and completes.
Any idea?
I'm using ios5.1 and ios6 on iphone 4
When you get called in the background in
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
(or ...exit)
just setup whatever you need for the server call (variables, payload for the server etc etc).
Before the actual sending start a
self.bgTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.bgTaskId];
self.bgTaskId = UIBackgroundTaskInvalid;
somelogger(#"ran out of time for background task");
// remember that we failed for the next time the app comes to the foreground
}];
Then do the actual sending with the HTTP framework of your choice and in the completion handlers reset the background task with
[[UIApplication sharedApplication] endBackgroundTask:self.bgTaskId];
Example using AFNetworking:
[self.httpClient postPath:#"state" parameters:#{#"abc": abc, #"value": val, #"h": h, #"app":myAppName , #"version": myAppVersion }
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (operation.response.statusCode != 200) {
DDLogVerbose(#"response was not 200. error: %i", operation.response.statusCode);
} else {
DDLogVerbose(#"success");
}
[[UIApplication sharedApplication] endBackgroundTask:self.bgTaskId];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
DDLogVerbose(#"request error %#, current retry count %d", error, retryCount );
// start our own retry mechanism
if (retryCount < MAX_RETRIES) {
retryCount++;
double delayInSeconds = RETRY_INTERVAL * (1 + (double)retryCount/10);
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// try again
});
} else {
// final
[[UIApplication sharedApplication] endBackgroundTask:self.bgTaskId];
// remember failure when app comes back to foreground
}
}];
I am using a
#property (assign, nonatomic) UIBackgroundTaskIdentifier bgTaskId;
to store the background identifier.
The whole mechanism is explained in http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW28
you have to request additional time if you want to stay alive.
see applle docu on background mode. There is a method for that.
generally you are not "allowed" to stay active in background for any task. only for specific ones, like GPS.
try to request additional background time after each region update.
If you havent already add 'location' to the UIBackgroundModes of your Info.plist. As a second idea I'd use AFNetworking which is widely popular and has backgrounding support. That means it will deal with setting up the parameters to tell the OS that it will "finish this thing before I go back to sleep".