2013年3月15日金曜日

blocksのclosures的特性を使ったGCDキャンセル法。

例えばゲームの当たり判定処理と画面描画処理のように「必ずこなさなくてはならないタスクと、処理能力によってはスキップすべき重いタスク」がセットになっている場合や、リアルタイム検索のように「ネットワーク検索タスクを実行するたびに、古いタスクが陳腐化してキャンセル処理が必要になる」などのケースで微妙に使えそうな気がします。

-(void) queue{
  static u_int32_t token;

  @synchronized(self) {
    token = arc4random_uniform(UINT32_MAX);
  }

  u_int32_t itsToken = token;

  dispatch_async(_serialQueue, ^{

    //タスク開始までにtoken値が更新されていれば、それ以上処理しない
    @synchronized(self) {
      if(token != itsToken) return
    }

    //重いタスク
    [self doHeavy];

  });
}

上記のqueueメソッドは呼び出す度に、静的ローカル変数であるtokenの値を、arc4random()関数を使ってランダムな値で書き換えます。そしてその値をローカル変数であるitsTokenにコピーします。

その後、dispatch_async()関数で、直列キューに対して何らかのタスクを与えます。

dispatch_async()に渡されたblocksは、コンテキストをキャプチャするときに、静的ローカル変数であるtokenは参照コピーをして、一方__block修飾子もないただのローカル変数であるitsTokenは、constとしてコピーします。

結果として、このblocksはtokenの値が更新されたかどうか(=キューに新たなタスクが積まれたかどうか)をitsTokenとの比較によって知ることができるのです。

応用例1

再描画命令など、タスク同士の実行間隔に時間の制約を付けたいときに、ローカル静的変数に最終処理時間を保持しておくとスマートに記述できます。

-(void) doOneSecondInterval{
  static NSTimeInterval lastProcessDate;
  
  dispatch_async(queue, ^{
    NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];

    //もし前回の処理から1秒未満ならリターン
    @synchronized(self) {
      if(lastProcessDate != 0 || now - lastProcessDate > 1) return;
    }

    [self doProcess];

    //最終処理時間を更新
    @synchronized(self) {
      lastProcessDate = [[NSDate date] timeIntervalSinceReferenceDate];
    }
  });
}

ブロックにキャプチャされたstatic変数は、__blocks修飾子がなくても読み書きが可能です。static変数は0で初期化されることを利用して、初回実行時には必ずdoProcessメソッドを呼ぶように判定を書く必要があります。

応用例2

例えば、データベースなど排他制御が必要な資源に対して、インスタンスの現在の状態を保存したいとき、stateSaveメソッドを呼び出した「その瞬間の状態」が保存されることが保証されて欲しいならば、コールスタックに一時コピーを作ってキャプチャさせるというテクニックが使えます。

-(void) stateSave{
  id instanceState = [[self.state copy] autorelease];

  dispatch_barrier_async(queue, ^{
    [databaseHelper writeData:instanceState];
  });
}

コピーされたオブジェクト、instanceStateはその場でautoreleaseされますが、ブロックにretainされ処理が完了するまでの生存が保証されます。そして、実際のブロックの実行時には既にself.stateが変更されていたとしても、キューがデータベースに保存するのはstateSaveが呼び出された瞬間のコピーなのです。


GCDとblocksの組み合わせは、思わぬ循環参照を招いて問題を起こすこともありますが、static変数、自動変数、__blocks修飾子変数、オブジェクトなどがそれぞれどのようにキャプチャされるのか、ということを理解して使いこなせば、非常に強力だと思います。

0 件のコメント:

コメントを投稿