Obj-Cで非同期処理の完了を待ちたいならブロック渡してコールバックだよね
移転しました →
Swiftは出たけどまだまだObjective-Cはオワコンってことにはならないと思うからObjective-Cの話を。
Objective-Cで非同期処理をしてその結果を受けてなんかしたい、ってときが結構あるんだけど待ち方がいまいちよくわからなかったことがあった。今思えば基本的なやり方だけどちゃんと残しておこう。
ちなみにテストなら前に書いたこれでよさそう。アプリケーションコードでやるとスレッド周りで整合性取れなくなるのか正しく動かなかった。それ以上は踏み込んで調べてはいない。
Objective-Cで非同期処理のテスト(依存ライブラリなし版) - PILOG
でもよく考えたら非同期処理が終わったことを同期的に知りたいというケースは実はそんなに無い気がしてきたので、最初の非同期処理が終わったあとにやりたい処理も非同期にしてしまえばいい。つまりブロック渡してコールバック。
例としてALAssetsLibrary
を使うケースを考える。ALAssetsLibrary
を使うクラスは自前のクラスにラップして使うとする。仮に名前をAssetsManager
とかなんとかいう名前にし、load
という読み出し用のメソッドがあるとする。
// AssetsManager.h - (void)load; - (UIImage*)latestImage; + (AssetsManager*)defaultManager; // Singleton
// AssetsManager.m - (void)load { _assets = [NSMutableArray new]; _assetsLibrary = [ALAssetsLibrary new]; [_assetsLibrary enumerateGroupsWithTypes:ALAssetsSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { // do something with group } failureBlock:^(NSError *error) { }]; }
このload
をViewController.m
をから呼び出し、完了後にUIImageViewを更新するみたいな流れを考える。
// ViewController.m - (void)updateImage { [[AssetsManager defaultManager] load]; self.imageView.image = [AssetsManager defaultManager] latestImage]; }
ところがこれではenumerateGroupsWithTypes
メソッドの呼び出し後すぐに画像更新の行に到達してしまうためうまくいかない。想定通りの挙動にするためにはenumerateGroupsWithTypes
が本当に終わったということを知る必要があるが、終わるのを待つよりもスマートな方法は完了時の処理をブロックで渡してやることになる。
コードで書くとこうなる。
// AssetsManager.h typedef void (^CompletionBlock)(void); - (void)loadWithCompletionBlock:(CompletionBlock)block; // 引数としてブロックを渡す - (UIImage*)latestImage; + (AssetsManager*)defaultManager; // Singleton
// AssetsManager.m - (void)loadWithCompletionBlock:(CompletionBlock)block { _assets = [NSMutableArray new]; _assetsLibrary = [ALAssetsLibrary new]; [_assetsLibrary enumerateGroupsWithTypes:ALAssetsSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { // do something with group block(); // ←この行 } failureBlock:^(NSError *error) { }]; }
そしてこの新しくなったload
を使う側はこうなる。
// ViewController.m - (void)updateImage { [[AssetsManager defaultManager] loadWithCompletionBlock:^{ self.imageView.image = [AssetsManager defaultManager] latestImage]; }]; }
これでうまくいく。