📜 ⬆️ ⬇️

Multitasking in iOS 7

Prior to iOS 7, developers were rather limited in what they can do when their applications remained in the background. In addition to VOIP and geolocation functions, the only way to perform code in the background was to use background tasks that were limited to control for a few minutes. If you wanted to upload a large video for viewing offline, or save the user's photos to the server, you could only do some of the work.

IOS 7 adds two new APIs to update your application's user interface and content in the background. The first, Background Fetch (background delivery or background update), allows you to receive new content from the network at regular intervals. The second, Remote Notifications, is a new feature that uses push notifications to notify when an event has occurred. Both of these new mechanisms help you to keep your application interface up to date, and can plan work on a new Background Transfer Service that allows you to perform out-of-the-process data transfer over the network (download and transfer).

Background Fetch and Deleted Notifications are simple application hooks every 30 seconds of time to do work until your application stops. They are not intended for processor-intensive work or long-term tasks; rather, they are for another long-running network request, such as downloading a large movie or performing quick content updates.
')
From the user's point of view, the only obvious change in multitasking is the new application switcher, which displays a snapshot of each application as it was when it was left in the background. But there is a reason for displaying snapshots - now you can update a snapshot of your application after completing work, showing a preview of the new content. Social networks, news, or weather apps can now display updated content, allowing the user to not open applications. We will see how to update the snapshot later.



Background fetch

Background Fetch is a kind of smart polling mechanism that is best suited for apps that have frequent content updates, such as social networks, news, or weather apps. The system wakes up the application based on user behavior, and updates it before the user starts the application. For example, if the user always uses the application in 1 day, the system recognizes and adapts, performing the update before the period of use. Background updates are merged to reduce battery usage, and if you report that new data was not available during the update, IOS can adapt using this information to avoid updating during inactivity.

The first step in providing Background Fetch is to specify that you will use the function in the UIBackgroundModes key in your information sheet. The easiest way to do this is to use the Capabilities tab in the Xcode 5 project editor, which includes background section modes for easily configuring multitasking capabilities.

image

Alternatively, you can edit the key manually:

<key>UIBackgroundModes</key> <array> <string>fetch</string> </array> 


Next, tell iOS how often you will update:

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; return YES; } 


By default, the update interval is never, so you need to set the time interval or the application will never be invoked in the background. The value ofUIApplicationBackgroundFetchIntervalMinimum asks the system to control so that your application wakes up as often as possible, but you must specify your own time interval if this is not necessary. For example, a weather application can only be updated hourly. IOS will wait at least the specified time interval between background updates.

If your application allows the user to exit, and you know that there will not be any new data, you can set minimumBackgroundFetchInterval back to UIApplicationBackgroundFetchIntervalNever to be a good citizen and to save resources.

The final step is to implement the following methods in your application:

 - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completionHandler(UIBackgroundFetchResultFailed); return; } //  response/data  ,      BOOL hasNewData = ... if (hasNewData) { completionHandler(UIBackgroundFetchResultNewData); } else { completionHandler(UIBackgroundFetchResultNoData); } }]; //   [task resume]; } 


Remember, you only have 30 seconds to determine if new content is available, process new content, and update the user interface. There should be enough time to fetch data from the network and draw several thumbnails for your user interface, but not much more. When your network requests are complete and your user interface has been updated, you should call the completion handler.

The completion handler serves two purposes. First, the system measures the power used by your processes and whether the new data was given are available based on theUIBackgroundFetchResult. Second, when the completion handler is called, the user interface snapshot is taken and the application manager is updated. The user will see the new content when he or she enters the application. This snapshot behavior is the completion handler common to all of the completion handlers in the new multi-tasking API.

In a real application, you must pass completionHandler into the application sub-components and name it when you have processed the data and updated the user interface.

At this point, you may be surprised at how IOS can capture the interface of your application, when it is running in the background, and how the life cycle of the application works with the Background Update. If your application is currently suspended, the system will wake it up before callingapplication: performFetchWithCompletionHandler :. If your application does not work, the system will launch it, calling the usual delegate methods, including application: didFinishLaunchingWithOptions :. You can think about it as the application works exactly as if the user launched it from Springboard, except when the UI is invisible.

In most cases, you will do the same work when the application is running in the background as when working in the foreground, but you can find out how the background starts by looking at the applicationState from UIApplication:

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); return YES; } 


Background Update Testing

There are two ways to simulate a background update. The easiest way to launch an application from Xcode and click Imitation Background Update in the Xcode menu in Debug and your application is running.

In addition, you can use a schema to change how Xcode starts your application. Under the Xcode Product menu item, select Scheme and then Manage Schemes. From here, edit or add a new scheme and check the launch of the eventcheckbox background as shown below.

image

Remote notifications

Remote notifications allow you to notify your application when important events occur. You may receive new instant messages for delivery, news alerts to send or the latest episode of your user's favorite TV show to download for offline viewing. Remote notifications are great for important content, where the delay between background updates may be unacceptable. Deleted Notifications can also be much more efficient than Background Update, since your application only launches when needed.

Remote notification is just an ordinary push notification with a label of available content. You can send a push with a message alert informing the user that something has happened while you update the user interface in the background. But Remote Notifications can also be silent, with no alert messages or sound used only to update the interface or background of your application's trigger. You could post local notifications when you have finished downloading and processing new content.

Quiet push notifications are limited in speed, so don't be afraid to send as much as your application needs. IOS and APNS servers will control how often they will be delivered, and you will not get into trouble by sending too much. If your Push notification is throttled, it may be delayed until the next time the device sends activity verification packets or receives another notification.

Sending Remote Notifications

To send a remote notification, check the box of available content in the push notification of the payload. Most push scripts and libraries already support remote notifications. When you send a remote notification, you can also include some data in the payload notification, so the application can refer to events. This can save you multiple network queries and an increase in the ability of your application.

I recommend using the Houston Nomad CLI utility to send provider messages when developing, but you can use your favorite library or script.

You can install Houston as part of nomad cli:

 gem install nomad-cli 


And then send the notification with the apn utility included in Nomad:

 # Send a Push Notification to your Device apn push <device token> -c /path/to/key-cert.pem -n -d content-id=42 


As a result, we will receive the following notification:

 { "aps" : { "content-available" : 1 }, "content-id" : 42 } 


IOS 7 adds a new deligirovaniya method that is called when we receive a Push notification with content — an available key.

 - (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"Remote Notification userInfo is %@", userInfo); NSNumber *contentID = userInfo[@"content-id"]; // Do something with the content ID completionHandler(UIBackgroundFetchResultNewData); } 


Again, the application runs in the background and gives 30 seconds to update the content and update its interface, before calling the completion handler. We could produce a quick network request, as we did in the background update, but let's use the new powerful transfer services to queue up large download tasks and see how we can update our user interface when it completes.

NSURLSession and Background Transfer Service

While NSURLSession is a new class in iOS 7, it also refers to a new technology in building connections. Designed to replace NSURLConnection, familiar concepts and classes such as NSURL, NSURLRequest, and NSURLResponse are preserved. You will work with replacing NSURLConnection in, NSURLSessionTask, to make network requests and process their responses. There are three types of session tasks — data, load and unload.

NSURLSession coordinates one or more of these NSURLSessionTasks and behaves in accordance with the NSURLSessionConfiguration with which it was created. You can create multiple NSURLSessions for group tasks related to the same configuration. To interact with the background transfer service, you will create a session configuration using [NSURLSessionConfiguration backgroundSessionConfiguration]. Tasks added to a background session are started in an external process and continue, even if your application is suspended, crashed or killed.

NSURLSessionConfiguration allows you to set HTTP headers by default, configure cache policy, restrict network usage, and much more. One option is thediscretionary flag, which allows the system to schedule tasks for optimal performance. What this means is that your transfers will only go via Wi-Fi when the device has enough power. If the battery is low, or only cellular is available, your task will not work. The discretionary flag affects only if the session configuration object was constructed by calling thebackgroundSessionConfiguration: method and if the background update is running while your application is in the foreground. If the transfer is initiated in the background, the transfer will always operate in discretionary mode.

Now we know a little about NSURLSession, and the Background Transfer Service, let's go back to our example of a remote notification and add code to put in the download queue for the background transfer services. After the download is complete, we will notify the user that the file is available for use.

NSURLSessionDownloadTask

First of all, let's handle Remote Notification and Enqueue anNSURLSessionDownloadTask on the phone transfer service. InbackgroundURLSession, we create a NURLSession with a background session configuration and add our application delegate as a session delegate. Documentary advice against multiple sessions with the same identifier, so we use dispatch_once to avoid potential problems:

 - (NSURLSession *)backgroundURLSession { static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *identifier = @"io.objc.backgroundTransferExample"; NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier]; session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; }); return session; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"Received remote notification with userInfo %@", userInfo); NSNumber *contentID = userInfo[@"content-id"]; NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]]; NSURL* downloadURL = [NSURL URLWithString:downloadURLString]; NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request]; task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", [contentID intValue]]; [task resume]; completionHandler(UIBackgroundFetchResultNewData); } 


We create a load task using the NSURLSession class method and set up our request to provide a description for later use. You should remember about the need to call the task to start, since all session tasks begin in a suspended state.
Now we need to implement the NSURLSessionDownloadDelegate methods to receive callbacks when the download is complete. You may also need to implement the ImplementNSURLSessionDelegate or NSURLSessionTaskDelegate methods if you need to perform authentication or other events in the session life cycle.

None of the methods of NSURLSessionDownloadDelegate delegates are required, although only one where we should take action in this example is [NSURLSession downloadTask: didFinishDownloadingToURL:]. After the download task is complete, you are provided with a temporary URL to the file on disk. You must move or copy the file to the storage of your application, then it will be removed from the temporary storage when you return from this delegate method.

 #Pragma Mark - NSURLSessionDownloadDelegate - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); // Copy file to your app's storage with NSFileManager // ... // Notify your UI } - (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 { } *) session downloadTask: (NSURLSessionDownloadTask *) downloadTask didWriteData: (int64_t) bytesWritten totalBytesWritten: (int64_t) totalBytesWritten totalBytesExpectedToWrite: (int64_t) totalBytesExpectedToWrite #Pragma Mark - NSURLSessionDownloadDelegate - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); // Copy file to your app's storage with NSFileManager // ... // Notify your UI } - (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 { } 


If your application is still running in the foreground, when the background tasks are completed, the above code will suffice. In most cases, however, your application will not work, otherwise it will be suspended in the background. In these cases, you need to implement two methods for delegates to use, so the system can wake up your application. Unlike previous delegate callbacks, an application delegate is called twice, because your sessions and tasks delegates may receive several messages.

Applying the application delegates method: handleEventsForBackgroundURLSession: is called before this NSURLSession delegate messages are sent, andURLSessionDidFinishEventsForBackgroundURLSession is called later. In the first method, you store the background completionHandler, and in the second, you call it to update the user interface:

 - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { // You must re-establish a reference to the background session, // or NSURLSessionDownloadDelegate and NSURLSessionDelegate methods will not be called // as no delegate is attached to the session. See backgroundURLSession above. NSURLSession *backgroundSession = [self backgroundURLSession]; NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession); // Store the completion handler to update your UI after processing session events [self addCompletionHandler:completionHandler forSession:identifier]; } - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { NSLog(@"Background URL session %@ finished events.\n", session); if (session.configuration.identifier) { // Call the handler we stored in -application:handleEventsForBackgroundURLSession: [self callCompletionHandlerForSession:session.configuration.identifier]; } } - (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier { if ([self.completionHandlerDictionary objectForKey:identifier]) { NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n"); } [self.completionHandlerDictionary setObject:handler forKey:identifier]; } - (void)callCompletionHandlerForSession: (NSString *)identifier { CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier]; if (handler) { [self.completionHandlerDictionary removeObjectForKey: identifier]; NSLog(@"Calling completion handler for session %@", identifier); handler(); } } 


This is a two-step process — you need to update the user interface of the application, if you are not in the foreground when the background transfer is over. In addition, if the application is not running at all, IOS will run it in the background, and the previous applications and sessions delegate the methods called after: didFinishLaunchingWithOptions:

Customization and restrictions

We briefly touched on the power of background rendering, but you should study the documentation and consider the options of NSURLSessionConfiguration that best support your use case. For example, the NSURLSessionTasks resource supports timeouts via the timeoutIntervalForResourceproperty in the NSURLSessionConfiguration. You can use this if your content is available only for a limited time, or if a failure to download and upload content within a given TimeInterval indicates that the user does not have sufficient Wi-Fi bandwidth.

In addition, to download tasks, NSURLSession fully supports uploading tasks, so you can upload videos to your server in the background and ensure that the user no longer needs to keep the app working, as you would do in iOS 6. A nice touch to set the property sessionSendsLaunchEvents from yourNSURLSessionConfiguration to NO if your application does not require running in the background when the transfer completes. Efficient use of system resources keeps both IOS and user happy.

Finally, there are several limitations to using background sessions. As required by the delegate, you cannot use a simple block based onNSURLSession callback methods. The background transfer only supports HTTP and HTTPS, and you cannot use custom protocols. The system optimizes transmission based on available resources, and you cannot force your transfer to progress in the background continuously.
Also note that NSURLSessionDataTasks are not supported in background sessions in general, and you should use only these tasks for short-lived, small queries, and not for loading or adding.

Summary

New powerful multi-tasking and network interfaces in iOS 7 have opened up a whole range of possibilities for new and existing applications. Consider use cases in your application that can benefit from your own process of transferring data over the network and updating data, and make the most of these fantastic new APIs. In general, carrying out background transfers, as if your application was working in the foreground, making appropriate updates to the user interface, and most of the work is already done for you.

• Use the appropriate new API to contain your application.
• Be efficient, and call completion handlers as early as possible.
• completion handlers update a snapshot of your application's user interface.

Source: https://habr.com/ru/post/215447/


All Articles