dispatch_source の DISPATCH_SOURCE_TYPE_TIMER で timeout 処理を実装する

先日、x秒たったらある処理をキャンセルするといういわゆるタイムアウト処理を実装する必要があったときに dispatch_source を使ってハマったので備忘録。

当時ググっても繰り返し一定間隔で処理を動かすサンプルはすぐ見つかったのでそれをベースやっても期待する動きならなかった。結局は「エキスパート Objective-C プログラミング」にサンプルがのってて助かりましたという話。

Xcode のスニペット形式だとこんな感じ

    /// dispatch_source を生成。timer を回す queue を指定。
    dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatch_queue#>);
    
    /// timeout の時間を "seconds" プレースホルダーに "15" のように入力。"leeway_seconds" にはズレの許容範囲を入力
    dispatch_source_set_timer(timerSource, dispatch_time(DISPATCH_TIME_NOW, <#seconds#>ull * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, <#leeway seconds#>ull * NSEC_PER_SEC);

    /// timer 発火時の挙動を実装
    dispatch_source_set_event_handler(timerSource, ^{
        
        <#code#>
        
        /// 後始末
        dispatch_source_cancel(timerSource);
        /* もしくは
#if !OS_OBJECT_USE_OBJC
        dispatch_release(timerSource);
#endif
        */
    });
    
    /// timer キャンセル時の挙動を実装
    dispatch_source_set_cancel_handler(timerSource, ^{
        <#something_clean_up#>
#if !OS_OBJECT_USE_OBJC
        dispatch_release(timerSource);
#endif
    });
    
    /// timer の動作開始
    dispatch_resume(timerSource);

素直に NSTimer を使えばいいじゃん?というのがあったんだけど、その処理全体がバックグランドディスパッチキュー上で実行されてたので Runloop を自分で回すのは嫌だったり、メインスレッドにタイマーをセットするとスレッドをまたぐ事になるのでシビアなタイミング(実行順序)を求められてたので dispatch_source を使いたかったというわけです。

ホントに「エキスパート Objective-C プログラミング」はすばらしい。