Стояла задача, чтобы программа отправляла через 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. Если у вас есть информация как лучше решить проблему с удержанием работы программы в фоне, буду очень рад.

Комментарии (20)


  1. aspcartman
    23.11.2015 20:11
    +1

    Enterprise program? Я лишь слышал, что там нет проверок эпл, и если это так, то решением будет fork() и совершение слежки в дочернем процессе.
    1. Регистрируем обработчики сигналов, внимательно смотрим, как система вас убивает и будет ли она трогать вас вообще. Я полагаю, что она будет убивать только процесс самого приложения, а сына оставит в покое.
    2. Если все замечательно и вас даже не убивают, то можно подобрать какуюнибудь точку рандеву, с помощью shmem, pipe или чего бы то ни было еще, что бы проверить, а нужно ли делать форк или сынок уже в работе.

    Это комментарий-предположение. Я полагаю, что никаких препятствий для такой реализации нет, но в этом не уверен.


    1. aspcartman
      23.11.2015 20:37
      +1

      Добравшись до гугла обнаружилось, что iOS блокирует вызов fork(). http://stackoverflow.com/questions/12088155/fork-failed-1-operation-not-permitted
      А жаль. Интересно, чего бы еще такого придумать в обход Вашего способа…


      1. egyp7
        24.11.2015 08:50

        Вероятно обходом будет самостоятельная имплементация fork если низкоуровневые апи не режутся MAC(Mandatory Access Control). Как вариант думаю еще можно заюзать posix_spawn(https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/posix_spawn.2.html). оно вроде не лочится никак. решение тоже очень простое:

        pid_t pid;
        char *argv[] = {
            "/private/var/mobile/Containers/Data/Application/392A2665-9019-33AC-2E3748331/testapp",
            "param1",
            "param2",
            NULL
        };
        
        posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
        waitpid(pid, NULL, 0);
        


        ps.: я бы мог еще кое что интересное описать, но как вижу малвар кодеров(даже тех кто пишет концепты) тут не жалуют, поэтому воздержусь.


        1. aspcartman
          24.11.2015 09:04

          posix_spawn режется. Имхо режутся все системные вызовы по номерам. Еще варианты?

          Да ладно, не жалуют, самые одаренные малвар-кодеры потом и пишут весь фундамент, на котором все работает. Ну, если в плохих мальчиков надоест играть. :3


  1. storoj
    23.11.2015 22:34
    +1

    раньше вроде был способ с постоянным проигрыванием «пустого» звука, но не знаю как оно ведет себя с другими плеерами вроде стандартного приложения Music


    1. AcidLynx
      24.11.2015 08:03

      Сейчас такой хак эппл при ревью приложения не пропускает.
      Равно как и использование для целей геолокаций опции VoiceOverIP.
      А вот Region Monitoring — то, что надо.


      1. aspcartman
        24.11.2015 08:56

        Пропускает или нет не важно
        а) Enterprise Program не имеет стадии ревью
        б) Все, что не пропускает ревью, можно зарелизить с помощью Dark Release.


        1. egormerkushev
          24.11.2015 10:29

          Просто придирка:
          б) Как вы зарелизите через Dark Release ключи в Info.plist?


          1. aspcartman
            24.11.2015 10:34

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


  1. storoj
    23.11.2015 22:39
    +2

    Поясните ещё почему

    Поставленную задачу надо решить только средствами iOS без изменения серверной части (никаких Push Notifications).

    Разве это не решило бы абсолютно все проблемы? Сервер когда хочет, тогда и опрашивает, можно менять эти интервалы. Приложение будет запущено если надо, всё отработает и сдохнет, если система так пожелает.


  1. scumware
    24.11.2015 03:34

    мде… сколько будут существовать устройства, столько будет существовать шпионский софт.
    кто заказчик-то? милиция? бандиты? ревнивый супруг? папаша-параноик?
    — ни за что бы не взялся за такую работу — противно.


    1. BasilioCat
      24.11.2015 09:29

      Представьте, что устройства принадлежат компании, она их выдает сотрудникам для использования в служебных целях в служебное время, и потому имеет полное право знать где они находятся.
      С другой стороны, по простому запросу из АНБ в apple/google, ваш собственный телефон будет сообщать где он находится без дополнительного софта. Более того, если вы не изменили настройки по умолчанию, он и так сообщает об этом, нужно только запросить историю ваших перемещений.
      Кстати, для папаш-параноиков существует возможность официально узнавать геопозицию своих чад встроенными в iOS средствами, если они привязаны к одному логину iCloud.


      1. egormerkushev
        24.11.2015 10:35
        +1

        Для контролем за детьми просто надо включить у них Find My Friends и дать себе доступ, даже одна учетка не обязательна.


        1. alexstz
          25.11.2015 14:48

          Работает она, надо заметить, хуже, чем Find My Phone: дольше время до отображения позиции и более редкие обновления.


  1. amaksr
    24.11.2015 06:53

    Я тоже сейчас пытаюсь портировать свое приложение KidsTrack с андроида на iOS, и решаю эту же задачу. Что интересно: на эмуляторе или на устройстве, подключенном через USB все работает прекрасно. При запуске на неподключенном ни к чему устройстве выясняется, что несмотря на то, что вызовы «didUpdateLocations» идут каждую секунду, через 10-15 минут работы в фоне что-то изменяется в системе, и код метода didUpdateLocations не может запустить NSURLConnection через dataTaskWithRequest. Ошибок никаких нет, просто перестает уходить трафик на сервер. Как только задача переходит в foreground, все NSURLConnection просыпаются, и все, что там было, отправляется. Не понятно пока, как это побороть. Я, правда, не использую ни Significant-change location, ни Region monitoring, ни Local Botifications


    1. AcidLynx
      24.11.2015 08:05
      +1

      > Я, правда, не использую ни Significant-change location, ни Region monitoring, ни Local Botifications
      Вот в этом и проблема :)
      Через 10-30 секунд ухода в фон приложение ставится на паузу. И если не отмечены необходимые Background Modes, то и сетевой стек тоже засыпает.


  1. InstaRobot
    24.11.2015 10:44
    +1

    Я кажется знаю заказчика. Это небольшой институт на Тверской? А делали трекер для полицейских. Угадал?


    1. VasKravchuk
      25.11.2015 17:23

      Программа делалась для курьеров, почтальонов и медсестер, которые работают «в поле» и центральный офис в зависимости от текущего местоположения оптимизирует распределение заказов:)


      1. InstaRobot
        25.11.2015 18:04

        Не угадал, значит! Просто недавно видел похожий заказ для полиции)


  1. AnthonyBY
    25.11.2015 19:23
    +1

    я бы ещё добавил, что когда мы добавляем permission на location update (в background mode), при публикации приложения в Apple Store нужно обязательно не забыть в конце дописать:
    “Continued use of GPS running in the background can dramatically decrease battery life.”

    ибо словить из-за этой ерунды rejection очень не приятно (особенно когда стартап)

    2.16

    We found that your app uses a background mode but does not include the following battery use disclaimer in your Application Description:

    “Continued use of GPS running in the background can dramatically decrease battery life.”

    It would be appropriate to revise your Application Description to include this disclaimer.