8

iOSで画面のオン/オフを処理するコードを作成しようとしています(同様の問題が議論されているこの質問を見てください)。OSXには同じシステム全体の通知機能があるため、この質問にOSXタグを含めました。また、以下で説明する問題は、通知機能への継承です (対 iOS または OSX への継承)。

システム全体の通知com.apple.springboard.hasBlankedScreenに登録して、画面がオフまたはオンになったときに通知を受け取る方法はよく知られています。

参考までに(システム全体の通知の登録に使用されるAPIは次のとおりです):

  • notify_post、notify_check_notify_get_stateなど
  • CFNotificationCenterPostNotification、CFNotificationCenterAddObserver など( notify_post などを内部で使用)

ただし、このアプローチには相互に関連する 2 つの問題があります。

  • 画面のオフとオンの両方の通知には、同じ名前が付けられます (com.apple.springboard.hasBlankedScreen)
  • オブザーバーは、通知の一部として状態を受け取りません。

したがって、結果として、画面のオンとオフが異なるソリューションを実装する必要があります (同じ通知コールバックが呼び出され、どのパラメーターも状態を持たないため)。

一般的に言えば、状態が通知コールバックから切り離されているという中心的な問題です。これを優雅に処理する方法がわかりません。

私は 2 つの簡単なアプローチを考え出しました (それぞれに欠陥があります)。また、別のアプローチのアイデアや、このアプローチの改善点を探しています。

計数液

すでに受け取った通知の数をカウントするカウンターを実装できます。この情報に基づいて、画面をオンまたはオフにするための通知であるかどうかがわかります (カウンターの奇数に基づいて)。

ただし、次の 2 つの欠点があります。

1) この場合、システムが (設計時の理由で不明なため) 同じ名前で追加の通知を送信すると、奇数チェックが壊れるため、ロジックが台無しになります。

2) また、初期状態を正しく設定する必要があります。したがって、コードのどこかに次のようなものがあります。

counter = getInitialState(); 
registerForNotification();

この場合、1 つの競合状態があります。システムが通知を送信し、getInitialState() を実行した後で状態を変更すると、registerForNotification() の前に間違ったカウンター値が返されます。

次のコードを実行する場合:

registerForNotification();
counter = getInitialState(); 

この場合、別の競合状態があります。registerForNotification() を実行した後、getInitialState() を実行する前に、システムが通知を送信して状態を変更した場合、カウンターを取得し、通知コールバックに入り、カウンターを増やします (これにより、エラーが発生します)。

通知受信時の状態判定ソリューション

この場合、カウンターは保存せず、通知コールバックで API notify_get_state を使用して現在の状態を取得します。

これには独自の問題があります。

1) アプリケーションに非同期で配信される通知。その結果、画面のオンとオフを非常に高速に切り替えると、画面が既にオンになっているときに両方の通知を受け取ることができます。したがって、notify_check は現在の状態を取得します (通知が送信された時点の状態とは異なります)。

その結果、アプリケーションが通知コールバックで notify_get_state を使用すると、「画面がオフになった」という 1 つの通知と「画面がオンになった」という通知ではなく、「画面がオンになった」という 2 つの通知があったと判断されます。

PS 一般的に言えば、説明されているすべての問題は、画面のオン/オフのケースに固有のものではありません。それらは、独特の状態を持ち、同じ通知名で送信されるシステム全体の通知に対して実際のものです。


更新 1

画面のオン/オフを非常に高速に切り替えて、notify_get_state() から同じ結果を取得するシナリオを正確にテストしませんでした。

ただし、2 つの通知com.apple.springboard.lockstate (CFNotificationCenterAddObserver を介してサブスクライブ) を受信し、別の API を使用して現在のデバイスのロック状態を取得し、両方の通知で同じ値を受信したとき、似たようなシナリオがありました。

したがって、notify_get_state も同じ値を返すというのは私の仮定にすぎません。しかし、私はそれが教育を受けた推測だと思います。notify_get_state の入力パラメーターは、2 つの呼び出しで同じになります (変更されません)。また、notify_get_state によって返される状態の FIFO キューがシステムに格納されているとは思いません。

4

1 に答える 1

10

それで、私は非常に簡単な実験を作成しました。私はこれをデバッガーの外でジェイルブレイクされたiOS6.1iPhone5で実行しました。

コード

次のコードを使用して、コンシューマーアプリを作成しました。

#define EVENT "com.mycompany.bs"

- (void)registerForNotifications {
    int result = notify_register_dispatch(EVENT,
                                          &notifyToken,
                                          dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0l),
                                          ^(int info) {
                                              uint64_t state;
                                              notify_get_state(notifyToken, &state);
                                              NSLog(@"notify_register_dispatch() : %d", (int)state);
                                          });
    if (result != NOTIFY_STATUS_OK) {
        NSLog(@"register failure = %d", result);
    }
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                    NULL, // observer
                                    notifyCallback, // callback
                                    CFSTR(EVENT), // event name
                                    NULL, // object
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
}

static void notifyCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    uint64_t state;
    notify_get_state(notifyToken, &state);
    NSLog(@"notifyCallback(): %d", (int)state);
}

したがって、ご覧のとおり、2つの異なるメソッドを使用して同じカスタムイベントに登録します。このアプリを起動し、イベントに登録してから、バックグラウンドに配置します(ホームボタンを押します)。

次に、ボタンを押すだけでイベントを生成できるプロデューサーアプリ:

double delayInSeconds = 0.001;

dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0l);
dispatch_async(q, ^(void) {
    notify_set_state(notifyToken, 2);
    notify_post(EVENT);        
});

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, q, ^(void){
    notify_set_state(notifyToken, 3);
    notify_post(EVENT);
}); 

結果

次に、プロデューサーアプリを実行し、約2秒ごとにイベントのペアを手動で生成しました。ご覧のとおり、プロデューサーは状態が。のイベントをすばやく投稿し、2すぐに状態がの別のイベントを投稿し3ます。したがって、これが完全に機能している場合、コンシューマーは両方のコールバックメソッド2について印刷する必要があり3ます。それは(あなたが恐れていたように)しません:

Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:12 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:18 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:22 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 2
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:26 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:30 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:33 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 2
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notify_register_dispatch() : 3
Feb 13 21:46:36 iPhone5 MyApp[1971] <Warning>: notifyCallback(): 3

CFNotificationSuspensionBehaviorCoalesce(すぐに配信するのではなく)使用する消費者登録方法を1つ変更してみました。結果:

Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:17 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:20 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:24 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:29 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:32 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:35 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 2
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:38 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 2
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notify_register_dispatch() : 3
Feb 13 21:48:39 iPhone5 MyApp[1990] <Warning>: notifyCallback(): 3

notify_register_dispatch()次に、コンシューマーのキューの優先度をバックグラウンドの優先度ではなくに変更してみました。結果:

Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:51 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3
Feb 13 21:49:53 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3

Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:55 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:49:59 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:01 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:04 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:06 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 2
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:09 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 2
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notify_register_dispatch() : 3
Feb 13 21:50:10 iPhone5 MyApp[2006] <Warning>: notifyCallback(): 3

結論(?)

  • あなたが疑ったように問題があります、そしてそれはSBGetScreenLockStatus電話だけではありません。時々、消費者は状態がに設定されているのを見たことがありません2
  • プロデューサーの時間遅延を5ミリ秒に増やした場合、問題は発生しませんでした。したがって、これは本当に時間的に近いイベントの場合にのみ問題になる可能性があります。画面のロック/ロック解除はおそらく大したことではありません。明らかに、遅い電話(iPhone <5)は異なる反応をします。
  • GCDコールバックブロックが優先度の高いキューに配置されていない限り、静的notifyCallback()メソッドが最初にコールバックされたようです。それでも、通常は静的コールバック関数が最初に呼び出されました。多くの場合、コールバックされた最初のメソッドは正しい状態(2)を取得しましたが、2番目のメソッドは取得しませんでした。したがって、問題に対処する必要がある場合は、最もパフォーマンスが高いと思われるコールバックメカニズムを選択する可能性があります(または、少なくとも、アプリ内でこれを自分でプロトタイプ化します)。
  • suspensionBehaviorパラメータが大きな違いを生んだとは言えません。とはいえ、 iOSがイベントを投稿する方法によっては、消費者の行動要求を無視する可能性のあるCFNotificationCenterPostNotificationのような呼び出しを使用している可能性があります。
  • このAppleドキュメントを見ると、2つのことがわかります。

    1. まず、notify_set_state非常に独創的なAPIの一部ではありませんでした
    2. 第二に、その文書の最初の段落は

ダーウィン通知APIリファレンス

これらのルーチンにより、プロセスはステートレス通知イベントを交換できます。

だから、たぶん私たちは元のデザインと一致しない何かをしようとしているだけです:(

  • AppleのNotificationPosterの例も見るnotify_get_statenotify_set_state、状態を伝達するために使用されていないことがわかります。ユーザー情報辞書として通知とともに渡します。明らかに、AppleのiOSイベントを監視している場合、イベントの投稿方法を制御することはできません。しかし、プロデューサーコンシューマーを作成できるアプリでは、私は遠ざかりnotify_set_stateます。
于 2013-02-21T00:20:06.050 に答える