| name | Objective-C Blocks and GCD |
| user-invocable | false |
| description | Use when blocks (closures) and Grand Central Dispatch in Objective-C for concurrent programming including block syntax, capture semantics, dispatch queues, dispatch groups, and patterns for thread-safe asynchronous code. |
| allowed-tools | [] |
Objective-C Blocks and GCD
Introduction
Blocks are Objective-C's closure implementation, providing anonymous functions
that capture surrounding context. Grand Central Dispatch (GCD) is Apple's
low-level API for managing concurrent operations using dispatch queues rather
than threads directly.
Blocks enable functional programming patterns, callbacks, and clean asynchronous
API design. GCD simplifies concurrency by abstracting thread management into
queues that automatically distribute work across available CPU cores. Together,
they form the foundation for modern Objective-C concurrent programming.
This skill covers block syntax and semantics, capture behavior, GCD queues and
dispatch functions, synchronization primitives, and patterns for safe concurrent
code.
Block Syntax and Usage
Blocks are first-class objects that encapsulate code and can capture variables
from their defining scope.
void (^simpleBlock)(void) = ^{
NSLog(@"Hello from block");
};
simpleBlock();
int (^addBlock)(int, int) = ^(int a, int b) {
return a + b;
};
int result = addBlock(5, 3);
NSString *(^greetBlock)(NSString *) = ^NSString *(NSString *name) {
return [NSString stringWithFormat:@"Hello, %@", name];
};
NSString *greeting = greetBlock(@"Alice");
- (void)fetchDataWithCompletion:
(void (^)(NSData *data, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [@"response" dataUsingEncoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(data, nil);
}
});
});
}
- (void)loadData {
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Data: %@", data);
}
}];
}
typedef void (^CompletionBlock)(BOOL success);
typedef void (^DataBlock)(NSData *data, NSError *error);
typedef NSString *(^TransformBlock)(NSString *input);
- (void)performOperationWithCompletion:(CompletionBlock)completion {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL success = YES;
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(success);
}
});
});
}
NSArray *blocks = @[
^{ NSLog(@"Block 1"); },
^{ NSLog(@"Block 2"); },
^{ NSLog(@"Block 3"); }
];
for (void (^block)(void) in blocks) {
block();
}
@interface AsyncOperation : NSObject
@property (nonatomic, copy) CompletionBlock completion;
@property (nonatomic, copy) DataBlock dataHandler;
@end
@implementation AsyncOperation
@end
Blocks must be copied when stored in properties or collections to move them from
stack to heap storage.
Block Capture Semantics
Blocks capture variables from their defining scope, with different behaviors for
different storage types and qualifiers.
void captureExample(void) {
NSInteger x = 10;
void (^block)(void) = ^{
NSLog(@"x = %ld", (long)x);
};
x = 20;
block();
}
void mutableCaptureExample(void) {
__block NSInteger counter = 0;
void (^incrementBlock)(void) = ^{
counter++;
};
incrementBlock();
incrementBlock();
NSLog(@"Counter: %ld", (long)counter);
}
@interface Counter : NSObject
@property (nonatomic, assign) NSInteger count;
- (void)incrementAsync;
@end
@implementation Counter
- (void)incrementAsync {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.count++;
});
}
@end
(, ) *timer;
- ()startTimer {
__ () weakSelf = ;
.timer = [ scheduledTimerWithTimeInterval:
repeats:
block:^( *timer) {
__ (weakSelf) strongSelf = weakSelf;
(!strongSelf) ;
[strongSelf updateUI];
}];
}
- ()updateUI {
();
}
- ()dealloc {
[.timer invalidate];
}
objectCaptureExample() {
*string = [ stringWithString:];
(^block)() = ^{
[string appendString:];
(, string);
};
block();
}
(, ) (^completion)( *data);
- ()fetchData {
__ () weakSelf = ;
.completion = ^( *data) {
__ (weakSelf) strongSelf = weakSelf;
(!strongSelf) ;
[strongSelf processData:data];
};
}
- ()processData:( *)data {
(, data);
}
blockObjectExample() {
__block *array = [ array];
(^addBlock)() = ^( object) {
[array addObject:object];
};
addBlock();
addBlock();
array = [ array];
}
Use __weak to avoid retain cycles when capturing self, and __block to allow
mutation of captured variables.
Dispatch Queues
GCD uses dispatch queues to manage concurrent execution, with serial queues
executing tasks sequentially and concurrent queues executing them in parallel.
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"On main thread");
});
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t defaultQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(defaultQueue, ^{
NSLog(@"Background work");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"UI update");
});
});
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"Task 1");
});
dispatch_async(serialQueue, ^{
NSLog(@"Task 2");
});
dispatch_queue_t concurrentQueue = dispatch_queue_create(
"com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"Concurrent task 1");
});
dispatch_async(concurrentQueue, ^{
();
});
__block *result;
(serialQueue, ^{
result = ;
});
(, result);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, * ),
dispatch_get_main_queue(), ^{
();
});
+ ()sharedInstance {
sharedInstance = ;
onceToken;
(&onceToken, ^{
sharedInstance = [[ alloc] init];
});
sharedInstance;
}
userInitiatedQueue = dispatch_get_global_queue(
QOS_CLASS_USER_INITIATED, );
utilityQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, );
(userInitiatedQueue, ^{
});
Use main queue for UI updates, global queues for background work, and custom
queues for synchronization and ordered execution.
Dispatch Groups
Dispatch groups coordinate multiple async operations, notifying when all tasks
complete.
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"Task 1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Task 2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Task 3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks complete");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"After wait");
dispatch_group_t manualGroup = dispatch_group_create();
dispatch_group_enter(manualGroup);
[self fetchDataWithCompletion:^(NSData *data, NSError *error) {
NSLog(@"Data fetched");
dispatch_group_leave(manualGroup);
}];
dispatch_group_enter(manualGroup);
[self fetchImageWithCompletion:^(UIImage *image, NSError *error) {
NSLog(@"Image fetched");
dispatch_group_leave(manualGroup);
}];
dispatch_group_notify(manualGroup, dispatch_get_main_queue(), ^{
NSLog(@"All fetches complete");
});
- (void)loadAllResources {
dispatch_group_t resourceGroup = dispatch_group_create();
__block NSData *userData = nil;
__block NSData *settingsData = nil;
__block UIImage *profileImage = nil;
dispatch_group_enter(resourceGroup);
[self fetchUserDataWithCompletion:^( *data) {
userData = data;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_enter(resourceGroup);
[ fetchSettingsWithCompletion:^( *data) {
settingsData = data;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_enter(resourceGroup);
[ fetchProfileImageWithCompletion:^( *image) {
profileImage = image;
dispatch_group_leave(resourceGroup);
}];
dispatch_group_notify(resourceGroup, dispatch_get_main_queue(), ^{
[ updateUIWithUser:userData settings:settingsData image:profileImage];
});
}
- ()fetchUserDataWithCompletion:( (^)( *))completion {
(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
(completion) completion([ data]);
});
}
- ()fetchSettingsWithCompletion:( (^)( *))completion {
(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
(completion) completion([ data]);
});
}
- ()fetchProfileImageWithCompletion:( (^)( *))completion {
(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
(completion) completion([[ alloc] init]);
});
}
- ()updateUIWithUser:( *)user settings:( *)settings
image:( *)image {
();
}
Dispatch groups are essential for coordinating multiple async operations and
ensuring all complete before proceeding.
Dispatch Barriers and Synchronization
Barriers provide synchronized access to shared resources in concurrent queues.
@interface ThreadSafeCache : NSObject
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;
@property (nonatomic, strong) NSMutableDictionary *cache;
@end
@implementation ThreadSafeCache
- (instancetype)init {
self = [super init];
if (self) {
self.concurrentQueue = dispatch_queue_create(
"com.example.cache",
DISPATCH_QUEUE_CONCURRENT
);
self.cache = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key {
__block id object;
dispatch_sync(self.concurrentQueue, ^{
object = self.cache[key];
});
return object;
}
- (void)setObject:(id)object forKey:(NSString *)key {
dispatch_barrier_async(self.concurrentQueue, ^{
self.cache[key] = object;
});
}
- (void)setObjectSync:(id)object forKey:(NSString *)key {
dispatch_barrier_sync(.concurrentQueue, ^{
.cache[key] = object;
});
}
- ()downloadImagesWithLimit:(< *> *)urls {
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, );
( *url urls) {
(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
(, url);
[ sleepForTimeInterval:];
dispatch_semaphore_signal(semaphore);
});
}
}
- ()processItems:( *)items {
dispatch_apply(items.count, dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^(size_t index) {
item = items[index];
(, index, item);
});
}
(, ) syncQueue;
(, ) count;
- ()init {
= [ init];
() {
.syncQueue = dispatch_queue_create(, DISPATCH_QUEUE_SERIAL);
.count = ;
}
;
}
- ()increment {
(.syncQueue, ^{
.count++;
});
}
- ()currentCount {
__block value;
(.syncQueue, ^{
value = .count;
});
value;
}
Barriers ensure exclusive write access while allowing concurrent reads, ideal
for thread-safe caches and data structures.
Block-Based APIs
Modern Cocoa APIs extensively use blocks for callbacks, providing cleaner
alternatives to delegate patterns.
- (void)fetchURL:(NSURL *)url {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Data received: %@", data);
});
}];
[task resume];
}
- (void)animateView:(UIView *)view {
[UIView animateWithDuration:0.3
animations:^{
view.alpha = 0.0;
view.transform = CGAffineTransformMakeScale(0.5, 0.5);
} completion:^(BOOL finished) {
if (finished) {
[view removeFromSuperview];
}
}];
}
- (void)observeNotifications {
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidEnterBackgroundNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSLog(@"App entered background");
}];
}
(^ProgressBlock)( progress);
(^CompletionBlock2)( success, *error);
- ()downloadFile:( *)url
progress:(ProgressBlock)progress
completion:(CompletionBlock2)completion;
- ()downloadFile:( *)url
progress:(ProgressBlock)progress
completion:(CompletionBlock2)completion {
(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
( i = ; i <= ; i += ) {
[ sleepForTimeInterval:];
(dispatch_get_main_queue(), ^{
(progress) {
progress(i / );
}
});
}
(dispatch_get_main_queue(), ^{
(completion) {
completion(, );
}
});
});
}
- ()downloadExample {
Downloader *downloader = [[Downloader alloc] init];
*url = [ URLWithString:];
[downloader downloadFile:url
progress:^( progress) {
(, progress * );
} completion:^( success, *error) {
(success) {
();
} {
(, error);
}
}];
}
Block-based APIs provide inline callback handling without the boilerplate of
delegation or notification observers.
Best Practices
-
Copy blocks when storing in properties to move them from stack to heap
and prevent crashes from dangling pointers
-
Use weak-strong dance for self capture in blocks stored as properties to
break retain cycles
-
Dispatch UI updates to main queue using dispatch_async to ensure
thread-safe UI modifications
-
Prefer dispatch groups over nested callbacks to coordinate multiple async
operations cleanly
-
Use dispatch barriers for reader-writer patterns to allow concurrent
reads while ensuring exclusive writes
-
Create custom queues for synchronization rather than using global queues
to avoid contention and priority issues
-
Check for nil before calling blocks to prevent crashes from unimplemented
optional block parameters
-
Use dispatch_once for thread-safe singletons to ensure exactly-once
initialization without locks
-
Limit concurrency with semaphores when accessing rate-limited resources
like network connections
-
Profile with Instruments to identify queue contention, thread explosion,
and performance bottlenecks
Common Pitfalls
-
Creating retain cycles with strong self capture in blocks stored as
properties causes memory leaks
-
Not copying blocks when storing them leads to crashes when stack-allocated
blocks go out of scope
-
Using dispatch_sync on current queue causes deadlock; never sync dispatch
to the queue you're on
-
Forgetting to dispatch to main queue for UI updates causes crashes or
undefined behavior
-
Overusing dispatch_sync blocks threads unnecessarily; prefer async
dispatch for better performance
-
Not balancing dispatch_group_enter/leave causes group notifications to
never fire or fire prematurely
-
Accessing mutable state without synchronization from multiple queues
causes race conditions and data corruption
-
Creating too many custom queues wastes resources; reuse queues where
appropriate
-
Using global queues for barriers doesn't work as barriers require custom
concurrent queues
-
Blocking in weak-strong dance without nil check can cause crashes if
weakSelf becomes nil during execution
When to Use This Skill
Use blocks and GCD when building iOS, macOS, watchOS, or tvOS applications that
require asynchronous operations, concurrent processing, or callback-based APIs.
Apply dispatch queues for background processing, network calls, file I/O, or any
operation that shouldn't block the main thread.
Employ dispatch groups when coordinating multiple async operations that must all
complete before proceeding, like loading multiple resources.
Leverage dispatch barriers for thread-safe data structures that support
concurrent reads and exclusive writes.
Use block-based APIs when designing modern Objective-C interfaces that provide
inline callback handling without delegate boilerplate.
Resources