0

I am working on an app that needs to regularly download data, when the app is in the foreground or background.

I have set up networking using NSURLSessionDownloadTask, such that it works fine in all situations but one.

When the app is running on a physical device, in the foreground and networking is on mobile data, the task resumes to running state, but never downloads any data, never gets any progress and never suspends.

It works fine if I switch on wifi, run from Xcode (debug or release) or run on a simulator.

I should also mention that it is intermittent; I could reproduce it yesterday, but not today, so it is possible that background networking is affected.

Edit - Example code

Due to the complexity of the application and the limited time I can give to this, I can't give a whole application, but I have included the relevant methods from the AppDelegate and JobsManager classes and the whole of the BackgroundJobFetcher class.

AppDelegate methods:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// other stuff

    [BackgroundJobFetcher sharedInstance];
}

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    [[BackgroundJobFetcher sharedInstance] setFetchCompletionHandler:completionHandler];
    [[JobsManager sharedInstance] fetchAllJobs];
}

- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
    [[BackgroundJobFetcher sharedInstance] setSavedCompletionHandler:completionHandler];
}

JobsManager methods:

- (void) initiateProcesses {
    self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:30 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [self fetchAllJobs];
    }];
}
- (void) fetchAllJobs {
    DefaultsManager *dManager = [DefaultsManager sharedDefaultsManager];
    NSString *apiFunction = @"getAllJobs";
    NSArray* optionsList = @[dManager.CustomerID, dManager.UserID];

    [self callAPIFunction:apiFunction options:optionsList];
}

- (void) callAPIFunction:(NSString*)apiFunction options:(NSArray*)options {
    NSString *apiBaseURL = @"https://www.*****.com/rest";
    NSString *urlComplete = [NSString stringWithFormat:@"%@/%@",
                             apiBaseURL,
                             apiFunction];

    for (NSString* option in options) {
        urlComplete = [NSString stringWithFormat:@"%@/%@", urlComplete, option];
    }

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlComplete]
                                             cachePolicy:NSURLRequestReloadIgnoringCacheData
                                         timeoutInterval:30.0];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[BackgroundJobFetcher sharedInstance] handleRequest:request];
    });
}

BackgroundJobFetcher.h:

#import <Foundation/Foundation.h>

@interface BackgroundJobFetcher : NSObject

@property (nonatomic, copy) void (^ _Nullable savedCompletionHandler)();
@property (nonatomic, copy) void (^ _Nullable fetchCompletionHandler)(UIBackgroundFetchResult);

+ (BackgroundJobFetcher*_Nonnull)sharedInstance;
- (void) handleRequest:(NSURLRequest*_Nonnull)request;
- (void) getAllTasksWithCompletionHandler:(void(^_Nonnull)(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks))completionHandler;
- (void) stopAllTasks;

@end

BackgroundJobFetcher.m:

#import "BackgroundJobFetcher.h"
#import "JobsManager.h"
#import "DefaultsManager.h"
#import "AppDelegate.h"

@interface BackgroundJobFetcher() <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>

@property (nonatomic, strong) NSMutableData* responseData;
@property (nonatomic, retain) NSURLSession *defaultSession;

@end

@implementation BackgroundJobFetcher

+ (BackgroundJobFetcher*)sharedInstance {
    static BackgroundJobFetcher* _sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedInstance = [BackgroundJobFetcher new];
        NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"MonitorJobFetcher"];
        sessionConfiguration.sessionSendsLaunchEvents = YES;
        sessionConfiguration.discretionary = YES;
        _sharedInstance.defaultSession = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                                       delegate:_sharedInstance
                                                                  delegateQueue:nil];
    });
    return _sharedInstance;
}

- (void) handleRequest:(NSURLRequest*)request {
    NSURLSessionDownloadTask* downloadTask = [self.defaultSession downloadTaskWithRequest:request];
    [downloadTask resume];
}

- (void) getAllTasksWithCompletionHandler:(void(^)(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks))completionHandler {
    [self.defaultSession getAllTasksWithCompletionHandler:completionHandler];
}

- (void) stopAllTasks {
    [self.defaultSession getAllTasksWithCompletionHandler:^(NSArray<__kindof NSURLSessionTask *> * _Nonnull tasks) {
        for (NSURLSessionTask* task in tasks) {
            [task cancel];
        }
    }];
}

#pragma mark NSURLSessionDelegate methods

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
    DLog(@"%@", error.localizedDescription);
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.savedCompletionHandler) {
        self.savedCompletionHandler();
        self.savedCompletionHandler = nil;
    }
}

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    DefaultsManager* dManager = [DefaultsManager sharedDefaultsManager];
    NSURLCredential *credential = [NSURLCredential credentialWithUser:dManager.Email
                                                             password:dManager.Password
                                                          persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential);
}

#pragma mark NSURLSessionTaskDelegate methods

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    if (!self.responseData) {
        self.responseData = [NSMutableData dataWithData:data];
    } else {
        [self.responseData appendData:data];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSString* urlString = task.originalRequest.URL.absoluteString;
    if (error) {
        DLog(@"error: %@", error.localizedDescription);
        [[JobsManager sharedInstance] requestFailedWithError:error fromURL:urlString];
    } else {
        [[JobsManager sharedInstance] jobsFetched:self.responseData];
    }

    self.responseData = nil;

    if (self.fetchCompletionHandler) {
        self.fetchCompletionHandler(UIBackgroundFetchResultNewData);
        self.fetchCompletionHandler = nil;
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    DefaultsManager* dManager = [DefaultsManager sharedDefaultsManager];
    NSURLCredential *credential = [NSURLCredential credentialWithUser:dManager.Email
                                                             password:dManager.Password
                                                          persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler {
    completionHandler(request);
}

- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task {
    DLog(@"URL: %@", task.originalRequest.URL.absoluteString);
    DLog(@"task.taskIdentifier: %lu", (unsigned long)task.taskIdentifier);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    [downloadTask resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    DLog(@"URL: %@", task.originalRequest.URL.absoluteString);
    DLog(@"task.taskIdentifier: %lu", (unsigned long)task.taskIdentifier);
    DLog(@"metrics: %@", metrics);
}

#pragma mark NSURLSessionDownloadDelegate methods

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    self.responseData = [[NSData dataWithContentsOfURL:location] mutableCopy];
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    DLog(@"downloadTask.taskIdentifier: %lu", (unsigned long)downloadTask.taskIdentifier);
    DLog(@"fileOffset: %lld", fileOffset);
    DLog(@"expectedTotalBytes: %lld", expectedTotalBytes);
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    DLog(@"downloadTask.taskIdentifier: %lu", (unsigned long)downloadTask.taskIdentifier);
    DLog(@"bytesWritten: %lld", bytesWritten);
    DLog(@"totalBytesWritten: %lld", totalBytesWritten);
}

@end
UglyBlueCat
  • 439
  • 1
  • 7
  • 22

1 Answers1

1

I believe I have found the answer in this so post.

I have removed the setting

sessionConfiguration.discretionary = YES;

It now appears to be working, although it is still in testing.

edit: testing has shown that this is indeed now working :)

UglyBlueCat
  • 439
  • 1
  • 7
  • 22