參考 Grand Central Dispatch In-Depth 的筆記。
Critical Section
Race Condition
Deadlock
Context Switch
背景讀取 dispatch_async PhotoDetailViewController.m - (void )viewDidLoad { [super viewDidLoad]; NSAssert (_image, @"Image not set; required to use view controller" ); self .photoImageView.image = _image; if (_image.size.height <= self .photoImageView.bounds.size.height && _image.size.width <= self .photoImageView.bounds.size.width) { [self .photoImageView setContentMode:UIViewContentModeCenter ]; } dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{ UIImage *overlayImage = [self faceOverlayImageFromImage:_image]; dispatch_async (dispatch_get_main_queue(), ^{ [self fadeInNewImage:overlayImage]; }); }); }
幾秒後提示 dispatch_after
- (void )showOrHideNavPrompt PhotoCollectionViewController.m { NSUInteger count = [[PhotoManager sharedManager] photos].count; double delayInSeconds = 1.0 ; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC )); dispatch_after(popTime, dispatch_get_main_queue(), ^(void ){ if (!count) { [self .navigationItem setPrompt:@"Add photos with faces to Googlyify them!" ]; } else { [self .navigationItem setPrompt:nil ]; } }); }
dispatch_after 最好使用在 main queue
Custom Serial Queue: Use caution when using dispatch_after on a custom serial queue. You’re better off sticking to the main queue.
Main Queue (Serial): This is a good choice for dispatch_after; Xcode has a nice autocomplete template for this.
Concurrent Queue: Use caution when using dispatch_after on custom concurrent queues; it’s rare that you’ll do this. Stick to the main queue for these operations.
Singleton PhotoManager.m + (instancetype )sharedManager { static PhotoManager *sharedPhotoManager = nil ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ sharedPhotoManager = [[PhotoManager alloc] init]; sharedPhotoManager->_photosArray = [NSMutableArray array]; }); return sharedPhotoManager; }
讀寫問題 dispatch_barrier 問題
PhotoManager.m - (void )addPhoto:(Photo *)photo { if (photo) { [_photosArray addObject:photo]; dispatch_async (dispatch_get_main_queue(), ^{ [self postContentAddedNotification]; }); } } - (NSArray *)photos { return [NSArray arrayWithArray:_photosArray]; }
dispatch_barrier 最好使用在 Custom Concurrent Queue
Custom Serial Queue: A bad choice here; barriers won’t do anything helpful since a serial queue executes one operation at a time anyway.
Global Concurrent Queue: Use caution here; this probably isn’t the best idea since other systems might be using the queues and you don’t want to monopolize them for your own purposes.
Custom Concurrent Queue: This is a great choice for atomic or critical areas of code. Anything you’re setting or instantiating that needs to be thread safe is a great candidate for a barrier.
修正
PhotoManager.m @interface PhotoManager ()@property (nonatomic ,strong ,readonly ) NSMutableArray *photosArray;@property (nonatomic , strong ) dispatch_queue_t concurrentPhotoQueue; @end sharedPhotoManager->_concurrentPhotoQueue = dispatch_queue_create("com.selander.GooglyPuff.photoQueue" ,DISPATCH_QUEUE_CONCURRENT); - (void )addPhoto:(Photo *)photo { if (photo) { dispatch_barrier_async(self .concurrentPhotoQueue, ^{ [_photosArray addObject:photo]; dispatch_async (dispatch_get_main_queue(), ^{ [self postContentAddedNotification]; }); }); } } - (NSArray *)photos { __block NSArray *array; dispatch_sync (self .concurrentPhotoQueue, ^{ array = [NSArray arrayWithArray:_photosArray]; }); return array; }
dispatch_sync 在讀取時,reader function 沒return時是無效的,所以採用dispatch_sync
讓 read 使用 sync 且與 write 在同一個 concurrent queeue 是確保 read & wirte 有順序,且避免多 thread 讀寫。
dispatch_sync 最好使用在 Custom Concurrent Queue
Custom Serial Queue: Be VERY careful in this situation; if you’re running in a queue and call dispatch_sync targeting the same queue, you will definitely create a deadlock.
Main Queue (Serial): Be VERY careful for the same reasons as above; this situation also has potential for a deadlock condition.
Concurrent Queue: This is a good candidate to sync work through dispatch barriers or when waiting for a task to complete so you can perform further processing.
全部圖片下載完後提示 dispatch_group PhotoManager.m - (void )downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock { dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{ __block NSError *error; dispatch_group_t downloadGroup = dispatch_group_create(); for (NSInteger i = 0 ; i < 3 ; i++) { NSURL *url; switch (i) { case 0 : url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; break ; case 1 : url = [NSURL URLWithString:kSuccessKidURLString]; break ; case 2 : url = [NSURL URLWithString:kLotsOfFacesURLString]; break ; default : break ; } dispatch_group_enter(downloadGroup); Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) { if (_error) { error = _error; } dispatch_group_leave(downloadGroup); }]; [[PhotoManager sharedManager] addPhoto:photo]; } dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); dispatch_async (dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(error); } }); }); }
dispatch_group 皆可使用在各種 Queue
Custom Serial Queue: This is a good candidate for notifications when a group of tasks completes.
Main Queue (Serial): This is a good candidate as well in this scenario. You should be wary of using this on the main queue if you are waiting synchronously for the completion of all work since you don’t want to hold up the main thread. However, the asynchronous model is an attractive way to update the UI once several long-running tasks finish such as network calls.
Concurrent Queue: This as well is a good candidate for dispatch groups and completion notifications.
比較簡潔方式
- (void )downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock { __block NSError *error; dispatch_group_t downloadGroup = dispatch_group_create(); for (NSInteger i = 0 ; i < 3 ; i++) { NSURL *url; switch (i) { case 0 : url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; break ; case 1 : url = [NSURL URLWithString:kSuccessKidURLString]; break ; case 2 : url = [NSURL URLWithString:kLotsOfFacesURLString]; break ; default : break ; } dispatch_group_enter(downloadGroup); Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) { if (_error) { error = _error; } dispatch_group_leave(downloadGroup); }]; [[PhotoManager sharedManager] addPhoto:photo]; } dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(error); } }); }
同時進行 for loop dispatch_apply - (void )downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock { __block NSError *error; dispatch_group_t downloadGroup = dispatch_group_create(); dispatch_apply(3 , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^(size_t i) { NSURL *url; switch (i) { case 0 : url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString]; break ; case 1 : url = [NSURL URLWithString:kSuccessKidURLString]; break ; case 2 : url = [NSURL URLWithString:kLotsOfFacesURLString]; break ; default : break ; } dispatch_group_enter(downloadGroup); Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) { if (_error) { error = _error; } dispatch_group_leave(downloadGroup); }]; [[PhotoManager sharedManager] addPhoto:photo]; }); dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(error); } }); }
但寫 app 時間有限,除非在非常大的 set 中,否則不要太 crazy。
Semaphores 問題:太浪費 cpu
GooglyPuffTests.m - (void )downloadImageURLWithString:(NSString *)URLString { NSURL *url = [NSURL URLWithString:URLString]; __block BOOL isFinishedDownloading = NO ; __unused Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *error) { if (error) { XCTFail (@"%@ failed. %@" , URLString, error); } isFinishedDownloading = YES ; }]; while (!isFinishedDownloading) {} }
dispatch_semaphore_create 方式
GooglyPuffTests.m - (void )downloadImageURLWithString:(NSString *)URLString { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0 ); NSURL *url = [NSURL URLWithString:URLString]; __unused Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *error) { if (error) { XCTFail (@"%@ failed. %@" , URLString, error); } dispatch_semaphore_signal(semaphore); }]; dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds); if (dispatch_semaphore_wait(semaphore, timeoutTime)) { XCTFail (@"%@ timed out" , URLString); } }
dispatch_source 蠻複雜的….
- (void )viewDidLoad { [super viewDidLoad]; #if DEBUG dispatch_queue_t queue = dispatch_get_main_queue(); static dispatch_source_t source = nil ; __typeof (self ) __weak weakSelf = self ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0 , queue); if (source) { dispatch_source_set_event_handler(source, ^{ NSLog (@"Hi, I am: %@" , weakSelf); }); dispatch_resume(source); } }); #endif