注:編集のさらに下には、元のプログラムの完全な複雑さなしに問題を生成する単純なコードがあります。
ジェイルブレイクされた iOS 用の目覚まし時計アプリをコーディングしようとしています。アラームをスケジュールするためのスタンドアロン アプリケーションとして UI をセットアップしました。これにより、アラーム情報がディスクに保存されます。保存ファイルは、常に実行されている起動デーモンによって読み取られ、アラームの実際のスケジュールを処理します。
私はそのようにアラームをスケジュールしています(編集:デーモンで)(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];
編集: 上記のコードは、によって呼び出される というメソッドで実行さcreateTimers
れreloadData
ます。reloadData
は共有保存ファイルからタイマーに関する情報を読み取り、 の init 関数で呼び出されます。また、管理者がUI アプリが保存ファイルを更新したAMMQRDaemonManager
という通知を ( で) 受け取るたびに呼び出されます。notify_post
soundAlarm:
メソッド(編集:デーモンでも)は次のとおりです。
- (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
}
これはうまく機能し、電話のロックが解除されているかロックされているかを警告します。しかし、電話機がディープ スリープに入るのに十分な時間ロックされている場合、タイマーは作動しません。
アラートを表示するだけでなく、サウンドも再生するので、必ずしも画面をオンにする必要はありません (それはいいことですが)。音。
何か案は?
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;
}
(メインアプリからの通知を登録して、保存ファイルをいつ読み取るかなどを知るコードは含まれていませんが、それは関係ないと思います)。
編集 (再度): で起動する実行ループにタイマーを追加しました[NSDate distantFuture]
。これにより、タイマーが長く保持されるようです (電話がロックされてから 1 分 45 秒後にスケジュールされたタイマーがオフになり、電話が起動されます) が、無期限ではありません (電話がロックされてから 7 分 30 秒後にスケジュールされたタイマーはオフになりませんでした) )。
編集:コードの他の部分との相互作用について心配することなく、問題を説明する次のおもちゃの例を作成しました。
このコードをコンパイルし、SSH 接続して実行し、電話をロックしました。を に変更する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
編集 (2013 年 3 月 31 日 5:50 EDT): 次のコードをおもちゃアプリのコードに追加して、GCD のdispatch_after
機能を使用するという Nate の提案を組み込みましたが、同じ時間の制約を受けるようです。追加のメモとして、メイン UI アプリは にインストールされ/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.");
});
編集 (3/31 午後 5 時 54 分): もう 1 つの簡単なメモ。次の行は、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