Многозадачность в iOS 7

в 21:34, , рубрики: iOS, ios development, ios programming, iOS SDK, ios7, разработка под iOS

До IOS 7, разработчики были довольно ограничены в том, что они могут сделать, когда их приложения оставались в фоновом режиме. Помимо VOIP и геолокационных функций, единственный способ для выполнения кода в фоновом режиме было использование фоновых задач, ограниченных к управлению в течении нескольких минут. Если вы хотели загрузить большое видео для просмотра в офф-лайн режиме, или сохранять фотографии пользователя на сервер, вы могли бы выполнить только часть работы.

IOS 7 добавляет два новых API-интерфейса для обновления пользовательского интерфейса вашего приложения и контента в фоновом режиме. Первый, Background Fetch (доставка в фоне или обновление фона), позволяет получать новый контент из сети через регулярные промежутки времени. Второй, Дистанционные Уведомления (Remote Notifications), это новая функция использует Push-уведомления, чтобы уведомить, когда произошло событие. Оба этих новых механизма помогают Вам сохранить интерфейс вашего приложения в актуальном состоянии, и могут планировать работу над новой Фоновой службой передачи, что позволяет выполнять вне — процесса передачи данных по сети (загрузка и передача).

Background Fetch и Удаленные Уведомления простые зацепки приложений каждые 30 секунд времени для выполнение работы пока Ваше приложение не приостановится. Они не предназначены для процессорной интенсивной работы или длительных задач, скорее, они для очередного давно запущенного запроса сети, как скачивание большого фильма или выполнения быстрых обновлений содержимого.

С точки зрения пользователя, единственное очевидное изменение в многозадачности является новый переключатель приложений, который отображает снимок каждого приложения каким оно было, когда его оставили в фоновом режиме. Но есть причина для отображения снимков — теперь вы можете обновить снимок вашего приложения после завершения работы, показывая предварительный просмотр нового содержания. Социальные сети, новости, или погодные приложения теперь могут отображать обновленное содержимое, позволяя пользователю не открывать приложения. Мы увидим, как обновить снимок позже.

Background Fetch

Background Fetch, является своего рода смарт-механизмом опроса, который лучше всего подходит для приложений, которые имеют частые обновления контента, такие как социальные сети, новости, или погодные приложения. Система «будит» приложение на основе поведения пользователя, и обновляет его прежде чем пользователь запустит приложение. Например, если пользователь всегда использует приложение в 1 дня, система узнает и адаптируется, исполняя обновление перед периодом использования. Фоновые обновления обьединяются, чтобы уменьшить использование батареи, а если вы сообщите, что новые данные не были доступны во время обновления, IOS может адаптироваться, используя эту информацию, чтобы избежать обновления во время бездействия.

Первым шагом в обеспечении Background Fetch это указать, что вы будете использовать функцию в ключе UIBackgroundModes в вашем информационном листе. Самый простой способ сделать это — использовать вкладку Capabilities в редакторе проекта Xcode 5, который включает в себя режимы раздела фон для удобной настройки многозадачных возможностей.

image

Как альтернатива, Вы можете редактировать ключ в ручную:

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

Далее, сообщите IOS как часто Вы будете обновлять:

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

    return YES;
}

По умолчанию интервал обновления — это никогда, так что вам нужно установить интервал времени или приложение не будет никогда вызываться в фоновом режиме. Значение ofUIApplicationBackgroundFetchIntervalMinimum просит систему управлять, чтобы ваше приложение просыпалось как можно чаще, но вы должны указать свой собственный временной интервал, если в этом нет необходимости. Например, погодное приложение может обновляться только ежечасно. IOS будет ждать по крайней мере указанный временной интервал между обновлением фона.

Если ваше приложение позволяет пользователю выйти, и вы знаете, что там не будет никаких новых данных, вы можете установить minimumBackgroundFetchInterval назад UIApplicationBackgroundFetchIntervalNever чтобы быть хорошим гражданином и для экономии ресурсов.

Последним шагом является реализация следующих методов в вашем приложении:

- (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];
}

Помните, у вас есть только 30 секунд, чтобы определить, является ли новый контент доступным, обрабатывать новое содержание, и обновить пользовательский интерфейс. Должно быть достаточно времени для выборки данных из сети и прорисовки несколько эскизов для вашего пользовательского интерфейса, но не намного больше. Когда ваши сетевые запросы являются полными и ваш пользовательский интерфейс был обновлен, вы должны вызывать обработчик завершения.

Обработчик завершения служит двум целям. Во-первых, система измеряет мощность, используемую вашими процессами и записи были ли новые даны доступны на основании theUIBackgroundFetchResult. Во-вторых, при вызове обработчика завершения, снимок пользовательского интерфейса берется и диспетчер приложений обновляется. Пользователь увидит новое содержание, когда он или она перейдет в приложение. Такое поведение мгновенных снимков обработчик завершения является общим для всех из обработчиков завершения в новых многозадачных API.

В реальном приложении, вы должны пройти completionHandler в суб-компоненты приложения и назовите его, когда вы обработали данные и обновили пользовательский интерфейс.

На данный момент, вы можете быть удивлены, как IOS можете снимать интерфейс вашего приложения, когда оно работает в фоновом режиме, и как жизненный цикл приложение работает с Фоновым Обновлением. Если ваше приложение в настоящее время приостановлено, система разбудит его перед callingapplication: performFetchWithCompletionHandler:. Если ваше приложение не работает, система запустит его, называя обычные методы делегата, includingapplication: didFinishLaunchingWithOptions:. Вы можете думать об этом как приложение работает в точности так же, как если бы пользователь запустил его от Springboard, кроме случаев когда UI невидим.

В большинстве случаев, вы будете выполнять ту же работу, когда приложение запускается в фоновом режиме что и при работе на переднем плане, но вы можете обнаружить как фон запускается, глядя на applicationState из UIApplication:

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

    return YES;
}

Тестирование фонового обновления

Есть два способа, чтобы имитировать фоновое обновление. Самый простой способ запустите приложение из Xcode и нажмите Имитация Фонового обновления в меню Xcode в Debug а ваше приложение работает.

Кроме того, можно использовать схему, чтобы изменить то, как Xcode запускает ваше приложение. Под Xcode пункт меню Product, выбрать Scheme, а затем Manage Schemes. Отсюда, редактировать или добавлять новую схему и проверьте запуск фона eventcheckbox как показано ниже.

image

Удаленные уведомления

Удаленные уведомления позволяют уведомить ваше приложение при возникновении важных событий. Возможно, вам придут новые мгновенные сообщения для доставки, новости оповещения для отправки или последний эпизод любимого телешоу вашего пользователя, чтобы загрузить для автономного просмотра. Удаленные уведомления прекрасно подходят для важного содержания, где задержка между фоновыми обновлениями может быть неприемлема. Удаленные Уведомления также могут быть гораздо более эффективным, чем Обновление Фона, так как ваше приложение запускает только при необходимости.

Удаленное уведомление – это просто обычные Push-уведомление с меткой доступного контента. Вы можете отправить Push с предупреждением о сообщении, информирующее пользователя, что что-то произошло, в то время как вы обновите пользовательский интерфейс в фоновом режиме. Но Удаленные Уведомления также могут быть тихими, не содержащими сообщений оповещения или звук, используемый только для обновления интерфейса или фона триггера работы вашего приложения. Вы могли бы размещать локальные уведомления, когда вы закончили загрузку и обработку нового содержания.

Тихие Push-уведомление являются ограниченными по скорости, так что не бойтесь отправлять столько, сколько потребуется приложению. IOS и серверы APNS будет контролировать, как часто они будут доставлены, и вы не попадете в беду отправив слишком много. Если ваши Push-уведомление дросселируется, оно может быть отложено до следующего раза пока устройство посылает пакеты проверки активности или принимает другое уведомление.

Отправка Удаленных Уведомлений

Для отправки удаленного уведомления, установите флажок доступного — контента в Push-уведомление полезной нагрузки. Большинство Push скриптов и библиотек уже поддерживают удаленные уведомления. Когда вы отправляете удаленного уведомления, вы также можете включить некоторые данные в уведомлении полезной нагрузки, поэтому приложение может ссылаться на события. Это может сэкономить вам несколько запросов сетей и повышение способности вашего приложения.

Я рекомендую использовать Houston утилиту Nomad CLI для отправки сообщений провайдера при разработке, но вы можете использовать вашу любимую библиотеку или скрип.

Вы можете установить Houston в рамках nomad-cli:

gem install nomad-cli

А потом пошлите уведомление с apn утилитой включенной в Nomad:

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

В результате получим следующее уведомление:

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

IOS 7 добавляет новый метод делигирования, который вызывается когда получаем Push – уведомление с контент – доступным ключом.

- (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);
}

Опять же, приложение запускается в фоновом режиме и дает 30 секунд, чтобы обновить контент и обновить свой интерфейс, перед вызовом обработчика завершения. Мы могли бы производить быстрый сетевой запрос, как мы делали в фоновом обновлении, но давайте использовать новые мощные службы передачи для постановки в очередь больших задач загрузки и посмотрим, как мы можем обновить наш пользовательский интерфейс, когда он завершает.

NSURLSession и Фоновая служба передачи

В то время как NSURLSession представляет собой новый класс в IOS 7, он также относится к новой технологии в построении связей. Предназначен для замены NSURLConnection, знакомые понятия и классы, такие как NSURL, NSURLRequest и NSURLResponse сохраняются. Вы будете работать с заменой NSURLConnection в, NSURLSessionTask, чтобы сделать сетевые запросы и обрабатывать их ответы. Есть три типа сессионых задач — данные, загрузки и выгрузки.

NSURLSession координирует один или несколько из этих NSURLSessionTasks и ведет себя в соответствии с NSURLSessionConfiguration, с которой он был создан. Вы можете создать несколько NSURLSessions для групповых задач, связанных с той же конфигурации. Для взаимодействия с фоновой службой передачи, вы создадите конфигурацию сеанса, используя [ NSURLSessionConfiguration backgroundSessionConfiguration ]. Задачи, добавленные в фоновой сессии запускаются во внешнем процессе и продолжаются, даже если ваше приложение приостанавливается, падает или убито.

NSURLSessionConfiguration позволяет установить заголовки HTTP по умолчанию, настраивать политику кэша, ограничить использование сотовой сети, и многое другое. Одним из вариантов является thediscretionary флаг, который позволяет системе планировать задачи для оптимальной производительности. Что это означает, что ваши переводы будут идти только через Wi-Fi, когда устройство имеет достаточную мощность. Если батарея разряжена, или только сотовая связь доступна, ваша задача не будет работать. Дискреционный флаг влияет только если объект конфигурации сеанса был построен вызовом thebackgroundSessionConfiguration: метод и если фоновое обновление запущено в то время как ваше приложение находится на переднем плане. Если передача инициируется фоново, то передача всегда будет работать в дискреционном режиме.

Теперь мы знаем, немного о NSURLSession, и Службе фоновой передачи, давайте вернемся к нашему примеру удаленного уведомления и добавим код для постановки в очередь загрузки на услуги трансфера фона. После завершения загрузки, мы будем уведомлять пользователя о том, что файл доступен для использования.

NSURLSessionDownloadTask

Прежде всего, давайте обращаться с удаленного Уведомления и Enqueue anNSURLSessionDownloadTask на услугифоновой передачи. InbackgroundURLSession, мы создаем NURLSession с конфигурации сеанса фона и добавим наш делегат приложения, как сессионный делегат. Документальные советы против нескольких сеансов с тем же идентификатором, поэтому мы используем dispatch_once, чтобы избежать потенциальных проблем:

- (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);
}

Мы создаем задачу загрузки, используя метод класса NSURLSession и настраиваем свою просьбу предоставить описание для последующего использования. Вы должны помнить о необходимости вызова запустить задачу, так как все задачи сессии начинаются в подвешенном состоянии.
Теперь нам нужно реализовать методы NSURLSessionDownloadDelegate, чтобы получать обратные вызовы при завершения загрузки. Вам также может понадобиться implementNSURLSessionDelegate или NSURLSessionTaskDelegate методы, если вам нужно выполнения проверки подлинности или другие события в сессионном жизненном цикле.

Ни один из методов NSURLSessionDownloadDelegate делегатов не являются обязательными, хотя только один, где мы должны принять меры в этом примере [ NSURLSession downloadTask: didFinishDownloadingToURL: ]. После завершения задачи загрузки, вы обеспечены временной URL к файлу на диске. Вы должны переместить или скопировать файл на хранение вашего приложения, тогда он будет удален из временного хранения, когда вы вернетесь из этого метода делегата.

#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
{
}

Если ваше приложение по-прежнему работает на переднем плане, когда фоновые задачи завершаются, приведенного выше кода будет достаточно. В большинстве случаев, однако, ваше приложение не будет работать, иначе он будет приостановлено в фоновом режиме. В этих случаях необходимо реализовать два метода применения делегатов, так что система может разбудить вашу заявку. В отличие от предыдущих обратных вызовов делегата, делегат приложения вызывается дважды, так как ваши сессии и задачи делегаты могут получить несколько сообщений.

Применение метода делегатов приложения: handleEventsForBackgroundURLSession: вызывается перед этим сообщения NSURLSession делегатов посылаются, andURLSessionDidFinishEventsForBackgroundURLSession вызывается позже. В первом методе, вы храните фоновое completionHandler, а во втором вы это вызываете, чтобы обновить пользовательский интерфейс:

- (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();
    }
}

Это двухступенчатый процесс — необходимо обновить пользовательский интерфейс приложения, если вы еще не на переднем плане, когда фоновая передача окончена. Кроме того, если приложение не запущено совсем, IOS запустит его в фоновом режиме, и предшествующие приложения и сеансы делегируют методы вызванные после: didFinishLaunchingWithOptions:

Настройка и ограничения

Мы кратко затронули силу фоновой передачи, но вы должны изучить документацию и рассмотреть опции NSURLSessionConfiguration, которые лучше всего поддерживают ваш ​​вариант использования. Например, NSURLSessionTasks ресурс поддерживает тайм-ауты через timeoutIntervalForResourceproperty в NSURLSessionConfiguration. Вы можете использовать это, если ваш контент доступен только в течение ограниченного времени, или если отказ для загрузки и выгрузки контента в пределах данного TimeInterval указывает, что пользователь не имеет достаточную пропускную способность Wi-Fi.

Кроме того, чтобы загрузить задачи, NSURLSession полностью поддерживает закачивание задач, так что вы можете загрузить видео на ваш сервер в фоновом режиме и обеспечить, чтобы пользователь больше не нуждался оставлять приложение работать, как можно было бы сделать в IOS 6. Приятная черта установить свойство sessionSendsLaunchEvents из yourNSURLSessionConfiguration в NO, если ваше приложение не требует запуска в фоновом режиме, когда передача завершает. Эффективное использование системных ресурсов держит обе IOS и пользователю счастливы.

Наконец, есть несколько ограничений в использовании фоновых сеансов. Как требует делегат, вы не можете использовать простой блок на основе методов обратного вызова onNSURLSession. Фоновая передача поддерживает только HTTP и HTTPS, и вы не можете использовать пользовательские протоколы. Система оптимизирует передачу на основе имеющихся ресурсов, и вы не можете заставить свой ​​трансфер к прогрессу в фоновом режиме постоянно.
Также отметим, что NSURLSessionDataTasks не поддерживаются в фоновых сессиях вообще, и вы должны использовать только эти задачи для короткоживущих, мелких запросов, а не для загрузки или добавлении.

Резюме

Новые мощные многозадачные и сетевые интерфейсы в IOS 7 открыли целый спектр возможностей для новых, так и существующих приложений. Рассмотрим варианты использования в своем приложении, которые могут извлечь выгоду из собственного процесса передачи данных по сети и обновления данных, и сделать большую часть этих фантастических новых API. В целом, проведения фоновых передач, как если бы ваше приложение работало на переднем плане, делая соответствующие обновления пользовательского интерфейса, и большая часть работы уже сделана для вас.

• Используйте соответствующий новый API для содержания вашего приложения.
• Будьте эффективными, и вызывайте обработчики завершения как можно раньше.
• обработчики завершения обновляют снимок пользовательского интерфейса вашего приложения.

Автор: yarmolchuk

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js