Можно ли использовать NSTimer для пробуждения взломанного iPhone из глубокого сна?

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

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

Я планирую будильник как таковой (EDIT: в NSDate *fireDate ) ( NSDate *fireDate вычисляется ранее):

 NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:fireDate interval:0 target:self selector:@selector(soundAlarm:) userInfo:alarm repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:singleTimer forMode:NSRunLoopCommonModes]; [self.timers addObject:singleTimer]; [singleTimer release]; 

EDIT: приведенный выше код работает в методе createTimers , который reloadData . reloadData считывает информацию о reloadData из общего файла сохранения и AMMQRDaemonManager функции init AMMQRDaemonManager , а также всякий раз, когда менеджер получает уведомление (с notify_post ) о том, что приложение пользовательского интерфейса обновило файл сохранения.

Метод soundAlarm: (EDIT: также в демоне):

 - (void)soundAlarm:(NSTimer *)theTimer { NSLog(@"qralarmdaemon: sounding alarm"); extern CFStringRef kCFUserNotificationAlertTopMostKey; CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(dict, kCFUserNotificationAlertTopMostKey, kCFBooleanTrue); CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFSTR("Title")); CFDictionaryAddValue(dict,kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK")); SInt32 err = 0; CFUserNotificationRef notif = CFUserNotificationCreate(NULL, 0, kCFUserNotificationPlainAlertLevel, &err, dict); CFOptionFlags response; if((err) || (CFUserNotificationReceiveResponse(notif, 0, &response))) { // do stuff } else if((response & 0x3) == kCFUserNotificationDefaultResponse) { // do stuff } CFRelease(dict); CFRelease(notif); // Do some other stuff } 

Это отлично работает и отображает предупреждение о том, разблокирован или заблокирован телефон. Но если телефон заблокирован в течение достаточного периода времени, чтобы войти в глубокий сон, таймер просто не срабатывает.

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

Есть идеи?


EDIT: вот main функция для демона.

 int main(int argc, char **argv, char **envp) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"qralarmdaemon: launched"); AMMQRDaemonManager *manager = [[AMMQRDaemonManager alloc] init]; NSTimer *keepRunningTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:1000 target:manager selector:@selector(keepRunning:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:keepRunningTimer forMode:NSRunLoopCommonModes]; // Execute run loop NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop run]; [manager release]; NSLog(@"qralarmdaemon: exiting"); [pool release]; return 0; } 

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


EDIT (снова): я добавил таймер в цикл выполнения, который запускается в [NSDate distantFuture] . Похоже, что сохранение таймеров дольше (таймер, запланированный на 1 минуту 45 секунд после того, как телефон был заблокирован, отключился и разбудил телефон), но не на неопределенный срок (таймер, запланированный на 7 минут, через 30 секунд после того, как телефон был заблокирован, не погас ).


EDIT: Я построил следующий пример игрушек, который иллюстрирует проблему, не беспокоясь о взаимодействии с другими частями моего кода.

Я скомпилировал этот код, SSH'd, и запустил его, а затем заблокировал свой телефон. Если я изменил dateByAddingTimeInterval:480 на dateByAddingTimeInterval:30 , я получаю следующий вывод:

 2013-03-31 12:21:25.555 daemontimertest[6160:707] daemon-timer-test: launched 2013-03-31 12:21:56.265 daemontimertest[6160:707] daemon-timer-test: timer fired 

Но когда он установлен на 480, я жду более 8 минут и вижу только первую строку:

 2013-03-31 12:08:09.331 daemontimertest[6049:707] daemon-timer-test: launched 

main.m :

 #import "MyClass.h" int main(int argc, char **argv, char **envp) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"daemon-timer-test: launched"); MyClass *obj = [[MyClass alloc] init]; NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[[NSDate date] dateByAddingTimeInterval:480] interval:0 target:obj selector:@selector(fireTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:singleTimer forMode:NSRunLoopCommonModes]; // Execute run loop [[NSRunLoop currentRunLoop] run]; [pool release]; return 0; } 

MyClass.m :

 #import "MyClass.h" @implementation MyClass - (void)fireTimer:(NSTimer *)theTimer { NSLog(@"daemon-timer-test: timer fired"); } @end 

EDIT (3/31/13 5:50 EDT): я добавил следующий код кода игрушечного приложения, чтобы включить предложение Nate об использовании функций dispatch_after GCD, но, похоже, оно подвержено тем же ограничениям времени. В качестве дополнительной заметки основное приложение пользовательского интерфейса установлено в /Applications а демон установлен в /usr/bin .

  double delayInSeconds = 10.0; NSLog(@"daemon-timer-test: delay is %f",delayInSeconds); dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"daemon-timer-test: time has passed."); }); 

EDIT (3/31 5:54 PM): Еще одно замечание. Следующие строки отображаются (не последовательно) в syslog прямо перед тем, как он начинает погружаться в глубокий сон, и больше нет сообщений, прежде чем я разбужу телефон. Я выбрал те, которые выглядят так, как будто они могут быть релевантными; последнее сообщение является последним, отправленным в syslog перед глубоким сном.

 Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageCanSystemSleep Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageSystemWillSleep ... Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone lockdownd[50]: 00343000 __63-[hostWatcher handleSleepNotification:service:messageArgument:]_block_invoke_0: Allowing Sleep Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: PM scheduled RTC wake event: WakeImmediate inDelta=645.40 Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: Idle Sleep Sleep: Using BATT (Charge:76%) ... Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: en0::stopOutputQueues ... Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: pmu wake events: menu 

2 Solutions collect form web for “Можно ли использовать NSTimer для пробуждения взломанного iPhone из глубокого сна?”

Короткий ответ

Да, возможно (я это сделал).

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

Сохранение демонов

Если вы посмотрите в документах Apple для запуска NSRunLoop :

Если к циклу запуска не подключены никакие входные источники или таймеры, этот метод немедленно прекращается; в противном случае он запускает приемник в NSDefaultRunLoopMode, повторно вызывая runMode: beforeDate :. Другими словами, этот метод эффективно запускает бесконечный цикл, который обрабатывает данные из входных источников и таймеров цикла цикла.

Ручное удаление всех известных источников входного сигнала и таймеров из цикла запуска не гарантирует, что цикл выполнения будет завершен. OS X может устанавливать и удалять дополнительные источники входных данных по мере необходимости для обработки запросов, направленных на поток получателя. Поэтому эти источники могут помешать запуску цикла.

В коде, который вы показываете для main программы демонов, вы не создаете (напрямую) какие-либо таймеры. Конечно, я не знаю, что вы делаете в [[AMMQRDaemonManager alloc] init] , поэтому, возможно, я ошибаюсь. Затем вы используете:

 NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop run]; 

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

В вашем комментарии говорится, что вы видите процесс демон, когда сигнал тревоги должен погаснуть. Тем не менее, мне интересно, может ли ваш процесс демонов умереть, а затем был перезапущен . Возможно, вы также можете показать нам файл .plist, который вы используете для своего запуска Daemon (который входит в /System/Library/LaunchDaemons ).

Одним быстрым экспериментом может быть не запуск вашего демона автоматически . Просто удалите файл LaunchDaemons папки LaunchDaemons и убедитесь, что вы убили этот процесс. Затем запустите его вручную из командной строки ssh'd в телефон:

 $ /Applications/MyApp.app/MyDaemon 

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

Решение?

Если выяснится, что у вас проблемы с этим, у меня есть проблемы, я попробую добавить таймер, который всегда запускается, когда вы это делаете. Если вы посмотрите на мой другой пример или учебник демонов Криса Альвареса , он показывает это. В daemon main() вы устанавливаете один NSTimer для run: метода run: . В этом методе run: вы можете использовать цикл while и sleep() . Или просто планируйте таймер повторять с небольшим интервалом.

Я также не знаю, как работает ваше приложение. Это только инструмент для планирования ( NSTimer ) аварийных сигналов? Если это так, возможно, что в любое время не может быть установлен аварийный сигнал. Возможно, другое решение, вместо того чтобы использовать UIApplication to notify_post() для передачи нового таймера демону, вы можете настроить демона просто посмотреть файл данных . UIApplication бы файл данных всякий раз, когда появляется новый таймер. Затем iOS может разбудить вашего демона, чтобы запланировать NSTimer .

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

Опубликуйте больше, если эти идеи не помогут вам исправить (может помочь тело [AMMQRDaemonManager init] ).

Обновить

Еще два предложения:

  • убедитесь, что ваше приложение (демон и пользовательский интерфейс) установлено в /Applications . Это обычное место для приложений для джейлбрейков, но я просто хотел убедиться, что вы не устанавливаете его в области песочницы .

  • попробуйте заменить реализацию NSTimer (для аварийных сигналов вы можете оставить main() таймер NSTimer daemon как есть) с блоками GCD:

  // you have used notify_post() to tell the daemon to schedule a new alarm: double delayInSeconds = 1000.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // put timer expiration code here }); 

Обновление II

Я также заметил, что в вашем исходном alarm: CFUserNotificationReceiveResponse() вы используете CFUserNotificationReceiveResponse() с бесконечным таймаутом. Это означает, что если пользователь не отклоняет всплывающее окно, обратный вызов таймера не будет завершен, и я считаю, что это означает, что никакие последующие запланированные ответные вызовы таймера могут не срабатывать. Вероятно, вы должны поместить весь код CFUserNotification в свой собственный метод (например, showPopup ), а затем showPopup обратный вызов таймера следующим образом:

 - (void)soundAlarm:(NSTimer *)theTimer { dispatch_async(dispatch_get_main_queue(), ^(void) { [self showPopup]; }); } 

Затем есть основная программа (в коде, который вы помещаете в Dropbox). Я бы рекомендовал изменить основной таймер (который вы вызываете непосредственно из main() ), чтобы быть повторяющимся таймером, с относительно небольшим интервалом, вместо того, чтобы использовать дату огня с помощью distantFuture . Если хочешь, ты ничего не сможешь сделать. Это всего лишь сердцебиение.

main.m:

 NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:5*60 // 5 minutes target:obj selector:@selector(heartbeat:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:singleTimer forMode:NSRunLoopCommonModes]; 

MyClass.m:

 - (void)heartbeat:(NSTimer *)theTimer { NSLog(@"daemon-timer-test: heartbeat timer fired"); } 

Мой последний комментарий: я не использую syslogd . Мне интересно, если какой-либо из ваших тестов терпит неудачу, а не потому, что таймеры не работают, а потому, что инструкции NSLog не отображаются в вашем файле журнала. Я выполнил все тесты, где я фактически запускаю исполняемый файл демона в командной строке, ssh'd в телефоне, и я просто смотрю консоль для вывода NSLog . Выйдите из списка возможных точек отказа …

Я разработал метод, который работает для меня. В соответствии с моим длинным обменом с Nate (и я определенно не смог бы выработать то, что происходило без его помощи), это, кажется, происходит автоматически в некоторых системах, но не на других. Проблема на моем телефоне, казалось, powerd в том, что powerd телефон в какой-то глубокий сон, который остановил NSTimer и не позволил им нормально стрелять.

Вместо того, чтобы отключать глубокий сон (который, как я подозреваю, имеет отрицательные последствия), я назначил событие с силовым эффектом:

 NSDate *wakeTime = [[NSDate date] dateByAddingTimeInterval:(delayInSeconds - 10)]; int reply = IOPMSchedulePowerEvent((CFDateRef)wakeTime, CFSTR("com.amm.daemontimertest"), CFSTR(kIOPMAutoWake)); 

Это успешно пробуждает телефон за 10 секунд до того, как сигнал тревоги должен погаснуть. (Интервал не точен.Я хотел, чтобы он был достаточно коротким, чтобы телефон не возвращался спать, но достаточно долго, чтобы, если телефон на мгновение проснулся, таймер все равно может идти в нужное время. вероятно, сократит его до 3 или 4 секунд.

Оставшаяся проблема заключается в том, что NSTimer для самого сигнала тревоги не будет обновляться автоматически, и поэтому он будет опоздать в любой период, когда телефон спал. Чтобы исправить это, вы можете отменить и перенести NSTimer всякий раз, когда телефон просыпается. Я сделал это, зарегистрировавшись для уведомления о том, что система управления питанием публикуется всякий раз, когда изменяется состояние питания:

 int status, notifyToken; status = notify_register_dispatch("com.apple.powermanagement.systempowerstate", &notifyToken, dispatch_get_main_queue(), ^(int t) { // do stuff to cancel currently running timer and schedule a new one here }); 

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

Надеюсь, это полезно для всех, кто боролся с этой проблемой.

  • Сохранить значение запроса
  • Как сделать телефонный звонок в Objective C?
  • Отобразить номер фракции в UILabel
  • GPUImage - ChromaKeyBlendFilter - sourceImage
  • Повторное использование контроллера View с использованием push и модальных сегментов
  • Подписание кода обхода с помощью Xcode 6
  • Отличительная выборка Core-data дает пустой массив
  • Основные данные: , будет ли этот метод отражать изменение объекта?
  • Предупреждение. Попытайтесь представить <GuessMeFinal> в <GuessMeFinal.ViewController>, чей вид не находится в иерархии окон -Swift
  • Утечка памяти при использовании запроса PHImageManagerImageForAsset
  • Последний в первый раз стек с GCD?
  • PhoneC: Разработка iOS проста с помощью XCode, Swift3, UITableView, cocatouch, давайте создадим приложения для iPhone, iPad и Macbook.