Pull to refresh

Фоновое обновление данных в iOS7

Reading time4 min
Views30K
В конце сентября компания APPLE выпустила iOS 7, одной из особенностей этой версии стала улучшенная многозадачность и возможность обновления данных, когда приложение не активно.
Есть два варианта запуска приложения для обновления данных — периодические обновления и запуск при получении специального push-уведомления. В каждом из вариантов приложение будет запущено в фоновом режиме (background mode), и будет принудительно закрыто через 30 секунд, так что времени для обновления будет совсем мало.

Периодическое обновление данных (Background fetch)


Чтобы разрешить приложению запускаться в фоновом режиме, нужно добавить в plist приложения ключ Required background modes со значением App downloads content from the network. Это так же можно сделать поставив галочку во вкладке Capabilities



Кроме того, нужно в методе application: didFinishLaunchingWithOptions: установить минимальный интервал для запуска.
If([[[UIDevice currentDevice] systemVersion] floatValue] >=7.0){
    [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:600];
}

(проверка на версию важна – в iOS 6 приложение падает на этой функции).
Вместо числового значения можно воспользоваться переменной UIApplicationBackgroundFetchIntervalMinimum, чтобы установить минимально возможный интервал, но особой роли это не играет, потому что устанавливается минимальный, а не гарантированный интервал. Когда конкретно приложение запустится – неизвестно. Оно будет запущено исходя из статистики работы: в котором часу приложение обычно запускается, сколько времени обычно находится в фоновом режиме. Чем меньше времени потребуется на работу программы, тем чаще она будет запускаться. Установка интервала обязательна, так как по умолчанию, это значение равно UIApplicationBackgroundFetchIntervalNever и запуска не произойдет.

При запуске приложение начинает свою работу в фоновом режиме, запуская метод application: performFetchWithCompletionHandler:

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
//DO SOMETHING
completionHandler(UIBackgroundFetchResultNewData); 
}

В конце метода необходимо вызвать полученный completionHandler с параметром UIBackgroundFetchResultNewData (варианты – UIBackgroundFetchResultNoData, UIBackgroundFetchResultFailed). Но в любом случае, через 30 секунд приложение будет принудительно закрыто.
Кроме этого, как и при стандартном запуске, вызывается application: didFinishLaunchingWithOptions:
и если в нем есть код, который не должен запускаться в данной ситуации (например, обновление UI), в целях экономии процессорного времени его лучше отключить. При запуске в этом режиме я не нашел дополнительных ключей в launchOptions, так что нужно проверять параметр application.applicationState, который будет равен UIApplicationStateBackground.
N.B. Если в течение 30 секунд, пока приложение работает в фоновом режиме, пользователь запустит приложение через UI, то повторного запуска этого метода не произойдет. Если необходимо, пропущенный код можно запустить в методе applicationWillEnterForeground:(UIApplication *)application

Для отладки запуска приложения в режиме фонового обновления в XCode есть возможность симуляции этого режима – включается галочкой в настройках схемы.



Запуск от push сообщения.


Второй возможностью получить обновление контента является запуск при получении push notification с выставленным флагом content-available.

Я не буду расписывать настройку приложения для получения push. Скажу только, что в plist приложения добавляется ключ Required background modes со значением App downloads content in response to push notifications (ну или соответсвующая галочка в настройках проекта), а к типам сообщений, которые приложение будет получать, добавляется новое значение
UIRemoteNotificationTypeNewsstandContentAvailability;

  UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeNewsstandContentAvailability;
    [application registerForRemoteNotificationTypes:myTypes];



При получении сообщения запускается метод

-(void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
    //DO SOMETHING
    completionHandler(UIBackgroundFetchResultNewData); 
}

которому тоже
будет дано всего лишь 30 секунд и в конце которого надо вызвать completionHandler с параметром.

Эта функция будет запущена вне зависимости от того, в каком состоянии сейчас приложение, даже если оно в активном режиме.
Если приложение запускается в фоне, то так же будет вызван метод application: didFinishLaunchingWithOptions:, но в данном случае в launchOptions будет запись с ключом UIApplicationLaunchOptionsRemoteNotificationKey.

Первый подводный камень – push не приходит, если кроме флага content-available ничего нет. Если добавить текст или звук, то все нормально. Поскольку никто не хочет спамить пользователя сообщениями, то посылаем пустой звук:
aps =     {
        "content-available" = 1;
        sound = "";
    };



Что можно успеть за 30 секунд?


В принципе, Apple не ограничивает нас в выборе метода, которым будет проводиться обновление. Однако, в iOS 7 появился новый класс NSURLSession, которым компания рекомендует воспользоваться. Не углубляясь в его описание можно сказать, что одним из его достоинств является возможность скачивания данных в фоновом режиме с помощью самой iOS, даже после прекращения работы приложения.

Ложка дегтя


А вот теперь немного личного опыта или почему для нашего приложения (электронная газета на iPad) все вышеперечисленное не сработало. Планировалось, что утром пользователь проснется, достанет iPad, а газета уже у него готова и ждет прочтения. Но…
Во-первых, фоновое обновление не работает когда устройство в спящем режиме (экран погашен). Обновление пройдет сразу же как только экран разблокируется, это хорошо для мессенжера, но для нас это слишком поздно.
Во-вторых, не смотря на то что в документации написано «If your app is suspended or not running, the system wakes up or launches your app and puts it into the background running state before calling the method.», push не запускает приложение, если оно в состоянии “not running”. Это значит, что если пользователь сам удалил приложение из списка задач или система выгрузила его, то push приходит, но приложение не запускается. Не ясно, баг это или нет, но что есть, то есть. Увы.

P.S. Чтобы приложение запускалось, необходимо так же разрешить возможность запуска в настройках устройства (или вернее, не запрещать, ибо по умолчанию это включено для новых программ). Проверить это из приложения можно через контроль значения [[UIApplication sharedApplication] backgroundRefreshStatus].
Tags:
Hubs:
+20
Comments12

Articles