Pull to refresh

Работа iOS App в фоновом режиме

Reading time 4 min
Views 44K
Стояла задача, чтобы программа отправляла через web socket текущие координаты по заданному пользователем интервалу. К тому же, программа должна работать в фоне и если пользователь или iOS по какой то причине её выгрузит из памяти, то желательно чтобы она перезапустилась и продолжила работу в фоне.
Поставленную задачу надо решить только средствами iOS без изменения серверной части (никаких Push Notifications).

Отправлять координаты по таймеру когда программа свернута в фон не составляет проблемы, для этого можно использовать background location mode для получения координат и long-running tasks для таймеров.

Но так как в iOS нет такой прелести как Android Background Services, то если вручную завершить программу, код перестает выполняться. Потому основная сложность заключается в том, как максимально быстро запустить программу в фоне, чтобы она продолжила выполнять свою задачу дальше, если её по каким то причинам выгрузила из памяти iOS, или если пользователь перезагрузил устройство, или если он вручную «убил» программу.

Теперь о том, что помогло решить данную задачу в приемлемом варианте:

— VOIP background mode
Когда включен этот режим фоновой работы, iOS перезапускает приложение после перезагрузки устройства или если программа была по каким то причинам выгружена из памяти самой iOS.
Так же, если программа свернута в фон, этот режим говорит iOS чтобы она не закрывала сокет соединения отмеченные флажком NSStreamNetworkServiceTypeVoIP.
Пример:

[NSStreamInstance setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];

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

— Significant-change location
Перезапускает программу в фоне по событию изменения координат если она была выгружена из памяти. Метод не совсем надежный, так как трудно предсказать в каком случае iOS запустит программу в фоне, это может произойти как сразу после завершения работы программы, так и через существенный промежуток времени (особенно если устройство не трогать).
Включается этот сервис с помощью вызова метода:

[CLLocationManagerInstance startMonitoringSignificantLocationChanges];

— Region monitoring location
Позволяет указать регион за входом или выходом из которого iOS будет следить и будить программу в случае этих событий.
Потому, как дополнительная мера, когда пользователь выгружает программу из памяти, по событию applicationWillTerminate мы устанавливаем регион для слежения с центром в текущих координатах и радиусом 5 метров (какое-то минимальное значение). И когда пользователь отходит, как показывает практика, где-то на 100 метров от центра заданного региона, iOS перезапускает программу по событию didExitRegion.

Данный метод работает намного точнее и надежнее чем significant-change location.
Пример:

@implementation AppDelegate
- (void)applicationWillTerminate:(UIApplication *)application {
        CLCircularRegion* region = [[CLCircularRegion alloc] initWithCenter:userLocaton.coordinate radius:5 identifier:@"wakeupinbg"];
        region.notifyOnEntry = YES;
        region.notifyOnExit = YES;
        [CLLocationManagerInstance startMonitoringForRegion:region];
    }
@end

— Local Notifications
Используется чтобы оповестить пользователя если программа вдруг выгружается из памяти и не перезапускается.
Работает так:
1) Устанавливается Local Notification которое прозвенит через 10 минут
2) Устанавливается таймер который каждые 7 минуты тикает и проверяет оставшееся время до того как прозвенит Local Notification
3) Если оставшееся время до Local Notification меньше чем 4 минуты, программа удаляет текущий Local Notification и устанавливает новый (пункт 1)
4) Если вдруг программу по какой-то причине выгрузили, то соответственно таймер не выполнится, Local Notification не переустановится (пункт 3 не выполниться), Local Notification прозвенит в обозначенное время и если программу не открыть, он будет звенеть каждую минуту и оповещать пользователя что программа выгружена из памяти и ее надо запустить.

Некоторые важные моменты использования данных подходов:

— Не забыть включить Background modes в настройках проекта, вкладка «Capabilities:



— Необходимо добавить поле NSLocationAlwaysUsageDescription в info.plist:



— После создания CLLocationManager, необходимо запросить разрешение на получение координат у пользователя:

[CLLocationManagerInstance requestAlwaysAuthorization];

— В iOS9 необходимо включить получение координат в фоне для объекта CLLocationManager:

if ([CLLocationManagerInstance respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
   [CLLocationManagerInstance setAllowsBackgroundLocationUpdates:YES];
}

Тестовый проект который можно запустить и посмотреть подробнее как работает данное решение — лежит вот тут: github.com/vaskravchuk/BGModesTest
Так же приложение с подобным решением (только без voip) есть на маркете: Track-kit.

P.S. Заказчик использует Apple Developer Enterprise Program, то есть программа не будет проходить проверку у Apple, потому возможные сложности с этим не учитывались.

P.S.S. Если у вас есть информация как лучше решить проблему с удержанием работы программы в фоне, буду очень рад.
Tags:
Hubs:
+14
Comments 28
Comments Comments 28

Articles