I'm trying to implement background fetch in an iOS app. In the official documentation is stated:
"You don’t have to do all background network activity with background sessions ... ... . Apps that declare appropriate background modes can use default URL sessions and data tasks, just as if they were in the foreground."
Moreover, in this WWDC 2014 video at 51:50 between the best practices is stated:
"One common mistake that we think some people have made in the past is assuming that when running in the background to handle things like a background fetch update... ...they have been required to use background session. Now, using background upload or download... ...works really well, but in particular for large downloads. When you're running for a background fetch update ... you have about 30 to 60 seconds to run in the background" [before the application gets suspended] "If you have same small networking task that could finish within this time it is perfectly ok to do this in an in-process or default NSURLSession ... ..."
I am exactly in the situation of a small networking task: I want to communicate with a rest api on our server, to post same data and get back the data response. I'm creating a single default session and executing one or two tasks, synchronising them using a dispatch group, like in this example:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"AppDelegate application:performFetchWithCompletionHandler:");
BOOL thereIsALoggedInUser = [self thereIsALoggedInUser];
if (!(thereIsALoggedInUser && [application isProtectedDataAvailable])){
completionHandler(UIBackgroundFetchResultNoData);
NSLog(@"completionHandler(UIBackgroundFetchResultNoData): thereIsALoggedInUser: %@, isProtectedDataAvailable: %@",
thereIsALoggedInUser ? @"YES" : @"NO",
[application isProtectedDataAvailable] ? @"YES" : @"NO");
return;
}
NSArray<NSString*> *objectsIWantToSend = [self getObjectsIWantToSend];
if (!objectsIWantToSend) {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
completionHandler(UIBackgroundFetchResultNoData);
NSLog(@"No post data");
} else {
[self sendToServer:objectsIWantToSend usingCompletionHandler:completionHandler];
}
}
-(void) sendToServer:(NSArray<NSString*> *)objectsArray usingCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"AppDelegate - sendToServer:usingCompletionHandler:");
__block BOOL thereWasAnError = NO;
__block BOOL thereWasAnUnsuccessfullHttpStatusCode = NO;
dispatch_group_t sendTransactionsGroup = dispatch_group_create();
NSURLSession *postSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
for (NSString *objectToPost in objectsArray) {
dispatch_group_enter(sendTransactionsGroup);
NSMutableURLRequest *request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"restApiUrl"]];
[request setHTTPMethod:@"POST"];
request = [Util setStandardContenttypeAuthorizationAndAcceptlanguageOnRequest:request];
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:@{ @"appData": objectToPost } options:kNilOptions error:nil]];
NSURLSessionDataTask *dataTask = [postSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSInteger statusCode = -1;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
statusCode = [(NSHTTPURLResponse *)response statusCode];
}
if (error) {
NSLog(@"Error");
thereWasAnError = YES;
} else if (statusCode != 201) {
NSLog(@"Error: http status code: %d", httpResponse.statusCode);
thereWasAnUnsuccessfullHttpStatusCode = YES;
} else {
[self parseAndStoreData:data];
}
dispatch_group_leave(sendTransactionsGroup);
}];
[dataTask resume];
}
dispatch_group_notify(sendTransactionsGroup, dispatch_get_main_queue(),^{
if (thereWasAnError) {
completionHandler(UIBackgroundFetchResultFailed);
} else if (thereWasAnUnsuccessfullHttpStatusCode) {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
completionHandler(UIBackgroundFetchResultNoData);
} else {
completionHandler(UIBackgroundFetchResultNewData);
}
});
}
In my application:didFinishLaunchingWithOptions: method I am setting the BackgroundFetchInterval to the MinimumInterval using
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
I am not force-quitting the app on the device because I know that the system in that case won't call the AppDelegate application:performFetchWithCompletionHandler: method until a user restarts the app
I'm using several test cases with several combinations of devices locked/unlocked, charging/not charging, all under a wifi network because they've got no sims
When I run the aforementioned kind of code using Xcode -> Debug -> Simulate background fetch or using a build scheme with the "launch due to a background fetch event" option selected all goes well: the server gets my post request, the app correctly has the server response back, and the dispatch_group_notify block gets executed. Testing the code through an actual device I get instead no result, not even the call to the server