iOS Long Running Background task

How to start iOS Long Running Background task

Due to background task time restrictions, iOS Background Task can not be run more than 10 minutes.In iOS 7.0, it is reduced to 3 minutes.

If you want make background task long running, you app should have any of the following Background Modes in the App’s PLIST.

1).App registers for location updates
2).App provides Voice over IP services
3).App plays audio
4).App processes Newsstand Kit downloads
5).App communicates using CoreBluetooth
6).App shares data using CoreBluetooth

Long Running Background Task Plist

 

Sample App PLIST file:

    <key>UIBackgroundModes</key>
    <array>
        <string>location</string>
        <string>voip</string>
        <string>audio</string>
        <string>newsstand-content</string>
        <string>bluetooth-central</string>
        <string>bluetooth-peripheral</string>
    </array>

Below is the sample long running background task with UIBackgroundModes is set to voip

#import <CoreLocation/CoreLocation.h>

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    CLLocationManager * manager = [CLLocationManager new];
     __block UIBackgroundTaskIdentifier background_task;

     background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
         [application endBackgroundTask: background_task];
         background_task = UIBackgroundTaskInvalid;
     }];

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

		  //run the app without startUpdatingLocation. backgroundTimeRemaining decremented from 600.00
	     [manager startUpdatingLocation];

         while(TRUE)
         {
         	//backgroundTimeRemaining time does not go down.

             NSLog(@"Background time Remaining: %f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
             [NSThread sleepForTimeInterval:1]; //wait for 1 sec
         }

         [application endBackgroundTask: background_task];
         background_task = UIBackgroundTaskInvalid; 
     });

}

 

Note: You need to click on OK, when the system asks for the confirmation while enabling Location Updates for the app.

allow Location Update to App

UPDATE:

The above methods is costly, it drains the battery. Better implementation is to use the combination of voip + audio.

Follow the steps to make the background task forever.
a). Play an empty background audio in infinite loop.
b). Listen for any audio interrupts and play the audio again.
Background audio is stopped when there is an interrupt(like phone call).
c). iOS can stop background audio at anytime. So Using VOIP API, reinitialize the background task. ( optional)
d). Set audio and voip background modes in the Info.plist
Below is the implementation of Long Running Background Task.
iOS Long Running Background Task

BackgroundTask.h Source

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
@interface BackgroundTask : NSObject
{
    __block UIBackgroundTaskIdentifier bgTask;
    __block dispatch_block_t expirationHandler;
    __block NSTimer * timer;
    __block AVAudioPlayer *player;

    NSInteger timerInterval;
    id target;
    SEL selector;
}
-(void) startBackgroundTasks:(NSInteger)time_  target:(id)target_ selector:(SEL)selector_;
-(void) stopBackgroundTask;

@end

BackgroundTask.m Source

#import "BackgroundTask.h"
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

void interruptionListenerCallback (void *inUserData, UInt32 interruptionState);

@implementation BackgroundTask

-(id) init
{
    self = [super init];
    if(self)
    {
        bgTask =UIBackgroundTaskInvalid;
        expirationHandler =nil;
        timer =nil;

    }
    return  self;

}

-(void) startBackgroundTasks:(NSInteger)time_  target:(id)target_ selector:(SEL)selector_
{
    timerInterval =time_;
    target = target_;
    selector = selector_;

    [self initBackgroudTask];
    //minimum 600 sec
    [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
        [self initBackgroudTask];
    }];

}
-(void) initBackgroudTask
{

    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
        if([self running])
            [self stopAudio];

         while([self running])
         {
             [NSThread sleepForTimeInterval:10]; //wait for finish
         }
         [self playAudio];
    });

}
- (void) audioInterrupted:(NSNotification*)notification
{
    NSDictionary *interuptionDict = notification.userInfo;
    NSNumber *interuptionType = [interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey];
    if([interuptionType intValue] == 1)
    {
        [self initBackgroudTask];
    }

}
void interruptionListenerCallback (void *inUserData, UInt32 interruptionState)
{
    NSLog(@"Got Interrupt######");
    if (interruptionState == kAudioSessionBeginInterruption)
    {
       /// [self initBackgroudTask];
    }
}
-(void) playAudio
{

    UIApplication * app = [UIApplication sharedApplication];
    NSString *version = [[UIDevice currentDevice] systemVersion];

    if([version floatValue] >= 6.0f)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
    }
    else
    {
        AudioSessionInitialize(NULL, NULL, interruptionListenerCallback, nil);

    }
    expirationHandler = ^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
        [timer invalidate];
        [player stop];
        NSLog(@"###############Background Task Expired.");
       // [self playMusic];
    };
    bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];

    dispatch_async(dispatch_get_main_queue(), ^{

        const char bytes[] = {0x52, 0x49, 0x46, 0x46, 0x26, 0x0, 0x0, 0x0, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x44, 0xac, 0x0, 0x0, 0x88, 0x58, 0x1, 0x0, 0x2, 0x0, 0x10, 0x0, 0x64, 0x61, 0x74, 0x61, 0x2, 0x0, 0x0, 0x0, 0xfc, 0xff};
        NSData* data = [NSData dataWithBytes:bytes length:sizeof(bytes)];
        NSString * docsDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

        // Build the path to the database file
        NSString * filePath = [[NSString alloc] initWithString:
                               [docsDir stringByAppendingPathComponent: @"background.wav"]];
        [data writeToFile:filePath atomically:YES];
        NSURL *soundFileURL = [NSURL fileURLWithPath:filePath];
        OSStatus osStatus;
        NSError * error;
        if([version floatValue] >= 6.0f)
        {

            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
            [[AVAudioSession sharedInstance] setActive: YES error: &error];

        }
        else
        {
            osStatus = AudioSessionSetActive(true);

            UInt32 category = kAudioSessionCategory_MediaPlayback;
            osStatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);

            UInt32 allowMixing = true;
            osStatus = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (allowMixing), &allowMixing );
        }

        player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:&error];
        player.volume = 0.01;
        player.numberOfLoops = -1; //Infinite
        [player prepareToPlay];
        [player play];
        timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:target selector:selector userInfo:nil repeats:YES];

    });
}

-(void) stopAudio
{
    NSString *version = [[UIDevice currentDevice] systemVersion];

    if([version floatValue] >= 6.0f)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
    }
    if(timer != nil && [timer isValid])
        [timer invalidate];

    if(player != nil && [player isPlaying])
        [player stop];

    if(bgTask != UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask=UIBackgroundTaskInvalid;
    }
}
-(BOOL) running
{
    if(bgTask == UIBackgroundTaskInvalid)
        return FALSE;
    return TRUE;
}
-(void) stopBackgroundTask
{
    [self stopAudio];
}
@end

Usage:

BackgroundTask * bgTask =[[BackgroundTask alloc] init];

//Star the timer
[bgTask startBackgroundTasks:2 target:self selector:@selector(backgroundCallback:)];
	// where call back  is -(void) backgroundCallback:(id)info

//Stop the task
[bgTask stopBackgroundTask];

Reference : iOS Multitasking