LoginSignup
3
5

More than 5 years have passed since last update.

NSRunLoopを立ち上げるベストプラクティス

Posted at

NSRunLoopを立ち上げるベストプラクティス

問題

NSRunLoopは個人的に以下の2点の仕様によって, 罠に嵌まることが多いように思う.

  • runModeが条件を満たしていないとイベントが発火しない
  • timerなどを紐付けたRunLoopが終了すると, 以降のイベントは発火しない

その為, View関係の処理を登録したい場合はmainRoopに, そうでない場合は自分でrunLoopを立ち上げることになると思う.
その際に散見されるのが以下の実装である.

while(!finish) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

だが, これだとReachabilityのようなイベントがごく稀 (環境によるが...) の場合は, finish = YESしたところでRunLoopが中々終了しない.
これは, distantFutureinput sourceが発火するまで待つように指示している為である.

では, 以下の要件を満たす為にはどうすれば良いのか. というのがこの記事の趣旨である.

  • 短期間のポーリングによる負荷は避けたい為, distantFuture指定は維持したい
  • 終了したいタイミングで, 出来る限り早く, 確実に終了したい.

解決策

/******** 起動時 *********/

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block NSRunLoop* runLoop = nil;
__block BOOL finish = NO;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    runLoop = [NSRunLoop currentRunLoop];
    dispatch_semaphore_signal(sem);

    while(!finish) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // runLoopに値が代入されるのを待つ為

/******* 終了時 ********/

finish = YES;
NSTimer* schedulerSignal = [NSTimer timerWithTimeInterval:0 repeats:NO block:^{ /* NOP */ }];
[runLoop addTimer:schedulerSignal forMode:NSDefaultRunLoopMode];

distantFutureinput sourceが発火する待つのであれば, input sourceが発火するようにすれば良いという話である.
また, finishsemaphoreを使う実装もあるが, BOOLは1stepでアクセスできるので, これについてはどちらでも変わらないと思われる.
(semaphoreではinput source代わりにはならないので, フラグ以上の意味はない)


捕捉: NSTiemr # timerWithTimeInterval:repeats:block:はiOS10.0+なので, それ以前の場合は適当に読み替えてください. Swiftの人もよしなに読み替えてください.

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5