Grand Central Dispatch (GCD) を活用した非同期処理の実用的な実装パターンを解説します。iOS開発における並行処理の最適化に必要な主要機能をコード例と共に紹介します。
遅延実行の実装
指定時間後にタスクを実行する場合、dispatch_after を使用します。キューの種類によって実行スレッドが決定されます。メインキューを指定するとメインスレッドで実行され、グローバルキューではバックグラウンドスレッドが利用されます。
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.5 * NSEC_PER_SEC));
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_after(delay, mainQueue, ^{
NSLog(@"メインスレッド: %@", [NSThread currentThread]);
});
出力結果:
2023-11-15 14:20:35.123 AppName[12345:678901] メインスレッド: <NSThread: 0x7fc0000b4a40>{number = 1, name = main}
シングルトン初期化パターン
dispatch_once はライフサイクル内で一回限りの実行を保証します。ビュー初期化時のリソース設定などに適しています。
- (void)viewDidLoad {
[super viewDidLoad];
static dispatch_once_t initToken;
dispatch_once(&initToken, ^{
NSLog(@"初期化スレッド: %@", [NSThread currentThread]);
// リソースの初期化処理
});
}
複数回呼び出されても、最初の実行時のみブロックが実行されます。
タスクグループによる並列処理
複数の非同期処理完了を待機する場合、dispatch_group を使用します。画像の並列ダウンロードと合成処理の実装例:
dispatch_group_t processingGroup = dispatch_group_create();
__block UIImage *firstImage = nil;
__block UIImage *secondImage = nil;
dispatch_queue_t downloadQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
dispatch_group_async(processingGroup, downloadQueue, ^{
firstImage = [self fetchImageFromURL:@"https://example.com/image1.jpg"];
});
dispatch_group_async(processingGroup, downloadQueue, ^{
secondImage = [self fetchImageFromURL:@"https://example.com/image2.jpg"];
});
dispatch_group_notify(processingGroup, dispatch_get_main_queue(), ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[secondImage drawInRect:CGRectMake(0, 0, 100, 100)];
[firstImage drawInRect:CGRectMake(100, 0, 100, 100)];
UIImage *combinedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 生成画像の利用
});
同期ポイントの制御
dispatch_barrier はキュー内のタスク順序を制御します。非同期/同期バージョンの動作差異を比較:
// 非同期バリア
dispatch_async(processingQueue, ^{ NSLog(@"処理A"); });
dispatch_async(processingQueue, ^{ NSLog(@"処理B"); });
dispatch_barrier_async(processingQueue, ^{ NSLog(@"同期ポイント"); });
dispatch_async(processingQueue, ^{ NSLog(@"処理C"); });
// 同期バリア
dispatch_async(processingQueue, ^{ NSLog(@"処理X"); });
dispatch_async(processingQueue, ^{ NSLog(@"処理Y"); });
dispatch_barrier_sync(processingQueue, ^{ NSLog(@"同期ポイント"); });
NSLog(@"メインスレッド継続");
非同期バリアでは後続タスクが待機されますが、同期バリアでは呼び出しスレッドがブロックされます。
並列イテレーションの最適化
dispatch_apply はループ処理を並列化します。並列キューでの実行例:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.processing", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, concurrentQueue, ^(size_t index) {
NSLog(@"インデックス:%zu スレッド:%@", index, [NSThread currentThread]);
});
出力結果はスレッド番号が非順序で表示され、並列処理が行われていることが確認できます。
セマフォによる同時実行制御
リソース競合を防ぐためのセマフォ実装。最大同時実行数を制限する場合:
dispatch_semaphore_t resourceLock = dispatch_semaphore_create(2);
for (NSUInteger i = 0; i < 10; i++) {
dispatch_semaphore_wait(resourceLock, DISPATCH_TIME_FOREVER);
dispatch_async(concurrentQueue, ^{
NSLog(@"実行:%lu スレッド:%@", (unsigned long)i, [NSThread currentThread]);
sleep(1); // リソース使用シミュレーション
dispatch_semaphore_signal(resourceLock);
});
}
セマフォの初期値を2に設定することで、最大2つのタスクが同時に実行されます。