2016年9月2日金曜日

非同期処理を同期処理に(dispatch_semaphore)

時として不便な非同期処理

画像ライブラリから1枚ずつ画像を読み込んで処理するコードを書いていたのだが、読み込み処理がblock構文を使った非同期処理なので、forループばかりどんどん空回りしてblock内の処理が後回しになり、期待通りの結果になってくれなかった。

for (int i = 0; i < imageURL.count; i++) {
      ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
      [library assetForURL:imageURL[i] resultBlock:^(ALAsset *asset) {
            //ここに読み込み成功時の処理を書く
      } failureBlock:^(NSError *error) {
            //ここにエラー処理を書く
      }];
}

非同期処理を同期処理にする技

これを、処理が一件終わるまで待たせるようにするのが同期処理だ。
dispatch_semaphore(ディスパッチ・セマフォ)ってのを使う。

dispatch = 使者や手紙などを送る
semaphore = 手旗信号

ということなので、それこそ処理の終了待ちを意味するフラグみたいなものをセットして並列処理を制限し、終わった時点でそれをクリアしてやるのだな。

for (int i = 0; i < imageURL.count; i++) {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
      [library assetForURL:tempURLArray[i] resultBlock:^(ALAsset *asset) {
          //ここに読み込み成功時の処理を書く
          dispatch_semaphore_signal(semaphore);
      } failureBlock:^(NSError *error) {
          //ここにエラー処理を書く
          dispatch_semaphore_signal(semaphore);
      }];
   });
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

}

dispatch_semaphore_create(0)でセマフォを作り、
dispatch_get_global_queue()内のblock構文の中に同期処理させたいコードを書き、
処理が済んだ時点でdispatch_semaphore_signal(semaphore)を実行して次の処理に移っている。
dispatch_semaphore_wait()はどれくらい処理を待つかだ。ここでは処理が終わるまでずっと待たせるようにしている。
古い参考サイトでは最後に変数semaphoreを解放してやる必要があるってなってたけど、ARC使ってる分には勝手に解放してくれるからいらない。

dispatch_async()ってのはasyncってくらいだから本来非同期処理にするための命令らしい(だから並列処理の優先順位をDISPATCH_QUEUE_PRIORITY_BACKGROUNDって指定してる)。
でもこう書くみたいだからいいの! 動いたからいいの! 他の参考サイトでもそう書いてるからいいの!

これを応用すれば同期処理も非同期処理に変更できるようだけど、それはまたいつか。

Swift3から(だと思う)は、書き方がObjective-Cの書き方とだいぶ変わってしまったので、別にまとめる。

0 件のコメント:

コメントを投稿