100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Blocks in Objective-C

Learn how Objective-C blocks capture surrounding variables as closures, how __block and __weak change capture behavior, and how to avoid retain cycles.

Control Flow & MethodsIntermediate11 min readJul 10, 2026
Analogies

What blocks are

A block is Objective-C's closure type, declared with the caret syntax ^(NSInteger x) { return x * 2; }, and assigned to a variable using a block type like NSInteger (^doubler)(NSInteger) = ^(NSInteger x) { return x * 2; };. Blocks capture the variables they reference from their enclosing scope by value at the time the block literal is created, meaning a local int captured inside a block reflects its value when the block was defined, not any later change to that same local variable outside the block.

🏏

Cricket analogy: A block capturing currentScore by value is like a commentator jotting down the score on a card the moment they start a sentence — even if the score changes mid-sentence, their captured note still reads the earlier number.

The __block qualifier

To let a block modify a captured local variable and have that change visible outside the block (or across multiple invocations of the same block), you must declare the variable with the __block storage qualifier, such as __block NSInteger counter = 0;, which moves the variable onto the heap (conceptually) so the block holds a reference to the actual variable rather than a snapshot copy. This is essential for patterns like accumulating a running total inside an enumeration block, e.g., [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { total += [obj integerValue]; }];, where total must be declared __block to compile and behave correctly.

🏏

Cricket analogy: __block NSInteger totalRuns is like a shared physical scoreboard every fielder can see and update live during the innings, rather than each fielder carrying their own private printed copy of the score.

Retain cycles: self and __weak

Under ARC, if a block referenced by an object's property (or ivar) captures self directly — for example self.completionHandler = ^{ [self doSomething]; }; — the block strongly retains self while self strongly retains the block via its property, creating a retain cycle that leaks memory because neither object's retain count can ever reach zero. The standard fix is to capture a weak reference outside the block and use it inside: __weak typeof(self) weakSelf = self; self.completionHandler = ^{ [weakSelf doSomething]; };, optionally re-strengthening it inside the block with __strong typeof(self) strongSelf = weakSelf; to avoid the object deallocating mid-execution of a multi-line block.

🏏

Cricket analogy: A block strongly capturing self is like a bowler and a specific fielder each refusing to leave the ground until the other does — a __weak reference is like the fielder agreeing to head to the pavilion once play ends, breaking the standoff.

objectivec
@interface DataLoader : NSObject
@property (nonatomic, copy) void (^completionHandler)(NSArray *results);
- (void)loadDataThenNotify;
@end

@implementation DataLoader

- (void)loadDataThenNotify {
    // AVOID: strong capture of self creates a retain cycle via completionHandler
    // self.completionHandler = ^(NSArray *results) { [self process:results]; };

    __weak typeof(self) weakSelf = self;
    self.completionHandler = ^(NSArray *results) {
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) return; // self was already deallocated
        [strongSelf process:results];
    };

    [self performFetchWithCompletion:self.completionHandler];
}

- (void)process:(NSArray *)results {
    NSLog(@"Processing %lu results", (unsigned long)results.count);
}

- (void)performFetchWithCompletion:(void (^)(NSArray *))completion {
    NSArray *fakeResults = @[@1, @2, @3];
    completion(fakeResults);
}

@end

// __block example: accumulating inside enumeration
NSArray<NSNumber *> *values = @[@10, @20, @30];
__block NSInteger total = 0;
[values enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) {
    total += obj.integerValue;
}];
NSLog(@"Total: %ld", (long)total);

Blocks stored as properties should be declared copy, not strong. A block literal may initially live on the stack; copying moves it to the heap so it remains valid after the enclosing scope that created it returns.

Block types can be simplified with a typedef, e.g. typedef void (^CompletionHandler)(NSArray *results);, which makes method signatures and property declarations involving blocks far more readable than repeating the full block syntax everywhere.

  • Blocks are closures declared with ^ that capture surrounding variables by value at creation time.
  • The __block qualifier lets a block mutate a captured variable, with changes visible outside the block too.
  • Block properties should be declared copy to ensure the block is moved to the heap and stays valid.
  • Capturing self strongly inside a block stored on self creates a retain cycle under ARC.
  • The standard fix is __weak typeof(self) weakSelf = self; used inside the block instead of self directly.
  • Re-strengthening with __strong typeof(self) strongSelf = weakSelf; guards against self deallocating mid-block.
  • A block typedef improves readability for block types used repeatedly across method signatures.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ObjectiveCStudyNotes#BlocksInObjectiveC#Blocks#Objective#Block#Qualifier#StudyNotes#SkillVeris#ExamPrep