An iOS 7 Background Transfer Service Tutorial

From Techotopia
Revision as of 19:57, 27 October 2016 by Neil (Talk | contribs) (Text replacement - "<table border="0" cellspacing="0">" to "<table border="0" cellspacing="0" width="100%">")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
PreviousTable of ContentsNext
iOS 7 Multitasking, Background Transfer Service and FetchingScheduling iOS 7 Local Notifications


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


For the purpose of demonstrating the new iOS 7 background transfer service and NSURLSession API, an application will be created in this chapter which, when launched, initiates the background download of a large image file. Once the file download has completed, the image will then be displayed within the application.


Contents


Creating the Example Project

Begin by launching Xcode and creating a new project using the Single View Application template. When prompted to do so, enter BackgroundTransfer as both the project name and class prefix and select either iPad or iPhone from the Devices menu depending on your preferred testing platform.

Within the project navigator panel, click on the uppermost BackgroundTransfer target entry and, in the main panel, select the Capabilities tab. Within the capabilities list, turn on Background Modes and enable the Background fetch option.

The handleEventsForBackgroundURLSession Method

The purpose of the background transfer service is to allow large downloads or uploads to take place even when the associated application is placed in the background. In the situation where a transfer is in progress and the application is placed in the background, the handleEventsForBackgroundURLSession: delegate method of the application’s delegate class will be called and passed a reference to a completion handler which must be called by the application when the transfer is complete. The next step in this tutorial, therefore, is to implement this method and set up a property in which to store a reference to the completion handler. Begin by selecting the BackgroundTransferAppDelegate.h file adding a property for the completion handler reference as follows:

#import <UIKit/UIKit.h>

@interface BackgroundTransferAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (copy) void (^sessionCompletionHandler)();
@end

Next, edit the BackgroundTransferAppDelegate.m file and add the delegate method so that the completion handler reference is stored:

#import "BackgroundTransferAppDelegate.h"

@implementation BackgroundTransferAppDelegate

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier
  completionHandler:(void (^)())completionHandler
{
        self.sessionCompletionHandler = completionHandler;
}
.
.
.
@end

Designing the User Interface

Select the Main.storyboard file and drag and drop an Image View object onto the canvas, resizing it to fill the entire view. With the image view selected in the canvas, select the Xcode Editor -> Resolve Auto Layout Issues -> Reset to Suggested Constraints menu option. This will add constraints attaching the image view to each side of the superview.

With the Image View object still selected, display the Attributes Inspector and change the Mode property to Aspect Fit. Display the Assistant Editor and ensure that it is displaying the content of the BackgroundTransferViewController.h file. Ctrl-click on the Image View in the canvas and drag the resulting line to a position just beneath the @interface line in the Assistant Editor panel. Release the line and establish an outlet for the image view named imageView.

With the BackgroundTransferViewController.h file still displayed, further modify the file to add properties to store the NSURLSession and NSURLSessionDownloadTask instances and to declare that the class implements a number of delegate protocols:

#import <UIKit/UIKit.h>

@interface BackgroundTransferViewController : UIViewController
<NSURLSessionDelegate, NSURLSessionTaskDelegate, 
                     NSURLSessionDownloadDelegate>

@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (nonatomic) NSURLSession *session;
@property (nonatomic) NSURLSessionDownloadTask *downloadTask;
@end

Configuring the View Controller

The next step is to begin to implement the functionality in the view controller class. First, edit the file and import the App Delegate interface file (access to the app delegate instance will be needed later to access the reference to the completion handler) and declare the URL to the image file that is to be downloaded:

#import "BackgroundTransferViewController.h"
#import "BackgroundTransferAppDelegate.h"

static NSString *DownloadURLString = 
               @"http://www.ebookfrenzy.com/code/LargeImage.jpg";

@interface BackgroundTransferViewController ()

@end

@implementation BackgroundTransferViewController
.
.
.
@end

Next, edit the viewDidLoad method in the BackgroundTransferViewController.m file to begin the initialization process:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURLSessionConfiguration *configuration = 
        [NSURLSessionConfiguration   
          backgroundSessionConfiguration:@"com.ebookfrenzy.transfer"];
    configuration.allowsCellularAccess = YES;

    _session = [NSURLSession sessionWithConfiguration:configuration 
         delegate:self delegateQueue:nil];

    NSURL *downloadURL = [NSURL URLWithString:DownloadURLString];

    NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
    
    _downloadTask = [self.session downloadTaskWithRequest:request];

    [_downloadTask resume];
}

The above code begins by creating a new NSURLSessionConfiguration instance, configured to perform a background transfer and specifying a unique identifier for the session (in this case the package name of the application):

NSURLSessionConfiguration *configuration = 
    [NSURLSessionConfiguration   
       backgroundSessionConfiguration:@"com.ebookfrenzy.transfer"];
configuration.allowsCellularAccess = YES;

By calling the backgroundSessionConfiguration method in the above code we are indicating that the session is required to perform file transfer related tasks. An option is also set to allow the transfer to use the cellular connection on the device if WiFi is not available. For large transfers, this should be set to NO, or the user given the opportunity to configure this setting to avoid incurring additional costs on their cellular data plan.

An NSURLSession instance is then created using the configuration object and designating the current class as the delegate for the session:

_session = [NSURLSession sessionWithConfiguration:configuration 
         delegate:self delegateQueue:nil];

Next, an NSURL object is created containing the URL of the image file to be downloaded and an NSURLRequest object initialized with this URL:

NSURL *downloadURL = [NSURL URLWithString:DownloadURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];

Finally, the download task request is created and resumed (tasks do not start at the point of creation and will not run until the resume method is called):

_downloadTask = [self.session downloadTaskWithRequest:request];
[_downloadTask resume];

All that remains is to implement the delegate methods.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Implementing the Session Delegate Methods

During the course of the background transfer process, a variety of methods on the delegate class will be called depending on the status of the transfer. Perhaps the most important is the method that gets called when the download is complete. For the purposes of this example, this method will copy the image file to the application’s Documents folder and display it on the image view. Remaining within the BackgroundTransferViewController.m file, implement this method as follows:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL
{
    NSLog(@"didFinishDownloadToURL: Copying image file");

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSArray *URLs = 
         [fileManager URLsForDirectory:NSDocumentDirectory 
               inDomains:NSUserDomainMask];
    NSURL *documentsDirectory = [URLs objectAtIndex:0];

    NSURL *fromURL = [[downloadTask originalRequest] URL];
    NSURL *destinationURL = 
       [documentsDirectory URLByAppendingPathComponent:[fromURL 
            lastPathComponent]];

    NSError *error;

    // Remove file at the destination if it already exists.
    [fileManager removeItemAtURL:destinationURL error:NULL];

    BOOL success = [fileManager copyItemAtURL:downloadURL 
        toURL:destinationURL error:&error];

    if (success)
    {
        UIImage *image = [UIImage 
              imageWithContentsOfFile:[destinationURL path]];
        _imageView.image = image;
    }
    else
    {
        NSLog(@"File copy failed: %@", [error localizedDescription]);
    }
}

Another important delegate method is the one that receives notification of any errors that may have occurred in the download process prior to completion. It is important to note that many errors are recoverable, so the method needs to check the error status to make sure that the error did not stop the download from completing successfully:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error == nil)
    {
        NSLog(@"Task %@ completed successfully", task);
    }
    else
    {
        NSLog(@"Task %@ completed with error: %@", task, 
          [error localizedDescription]);
    }
    _downloadTask = nil;
}

When the background session has completed, the following delegate method will be called. The sole task for this method in this example is to make a call to the completion handler, a reference to which was previously stored in the application’s delegate class:

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    BackgroundTransferAppDelegate *appDelegate = 
        (BackgroundTransferAppDelegate *)
                [[UIApplication sharedApplication] delegate];

    if (appDelegate.sessionCompletionHandler) {
        void (^completionHandler)() = 
                 appDelegate.sessionCompletionHandler;
        appDelegate.sessionCompletionHandler = nil;
        completionHandler();
    }
    NSLog(@"Task complete");
}

The final two delegate methods to implement are called when the transfer resumes and when a block of data has been written. Whether or not these methods need to actually do anything will depend on your application requirements. The write data method, for example, can be useful if the application is displaying some form of progress bar tracking the progress of the transfer:

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask 
 didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten 
 totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}

Testing the Application

Compile and run the application on the iOS Simulator and test that the image file downloads and displays while the application remains in the foreground. Stop and restart the application, this time placing it into the background as soon as it appears using the iOS Simulator Hardware -> Home menu option. Monitor the Xcode console panel until the Task complete message appears:

2013-08-13 16:39:46.247 BackgroundTransfer[893:1403] didFinishDownloadToURL: Copying image file
2013-08-13 16:39:46.421 BackgroundTransfer[893:1403] Task <__NSCFBackgroundDownloadTask: 0x8c1d480> completed successfully
2013-08-13 16:39:52.018 BackgroundTransfer[893:1403] Task complete

When the task completion message appears, return the application to the foreground, at which point the image should be displayed on the image view as shown in Figure 62-1:


An iOS 7 Background Transfer Example App Running

Figure 62-1

Summary

The iOS 7 Background Transfer Service in conjunction with the NSURLSession API allows the transfer of large files to be performed in the background and to continue even in the event that the application exits or the device reboots. In this chapter we have worked through the creation of a simple example designed to outline the steps involved in implementing a background file transfer operation using this service.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
iOS 7 Multitasking, Background Transfer Service and FetchingScheduling iOS 7 Local Notifications