AntiMoron
AntiMoron

Reputation: 1366

Why objective-c's block cannot capture values dynamically?

I wanna implement defer in objective-c. And here's my code:

/**
  RAII : ABC->~ABC
 */
@interface DeferImpl_ : NSObject
/**
 *  init with a callback
 *
 *  @return
 */
-(instancetype) initWithCallback:(void(^)())callback;
/**
 *  a callback
 */
@property(nonatomic, copy) void(^callback)();

@end

/**
 *  Defer
 *
 *  @param X { statement; statement; ... }
 *
 *  @return
 */
#define DEFER(X)  [[DeferImpl_ alloc] initWithCallback:^X]

#define SAFE_INVOKE(x) do{if(x){(x)();}}while(0)
@implementation DeferImpl_
/**
 *  invoke callback
 */
-(void) dealloc {
    SAFE_INVOKE(self.callback);
}

-(instancetype) initWithCallback:(void(^)())callback {
    self = [super init];
    self.callback = callback;
    return self;
}

@end

The implementation is simple and seems easy to use. But it's buggy!.

Here comes what I feel frustrated.

int main(void)
{
    NSInteger count = 0;
    DEFER({
        NSLog(@"Defer: %@", @(count));
    });
    count = 123;
    NSLog(@"Before defer block!");
    return 0;
}

Log is:

2017-01-12 17:31:32.401 test[73724:18571479] Defer: 0

2017-01-12 17:31:32.402 test[73724:18571479] Before defer block!

So is there anyone can tell me why count still 0 in the block?

Upvotes: 0

Views: 49

Answers (1)

Sulthan
Sulthan

Reputation: 130102

You have hidden most of your work into macros (not a good programming style!) and your are missing the basic point - DEFER calls the block immediately, before even reaching count = 123.

If you don't assign the result of [DeferImpl_ alloc] to any variable, it will get released immediately, also immediately calling the block.

Your current code is the same as writing directly:

NSInteger count = 0;
NSLog(@"Defer: %@", @(count));
count = 123;
NSLog(@"Before defer block!");

If you change your code to:

NSInteger count = 0;
id x = DEFER({
    NSLog(@"Defer: %@", @(count));
});
count = 123;
NSLog(@"Before defer block!");

Your result will be:

Before defer block!
Defer: 0

Now, why the value is still zero? Because blocks capture variables by value. To capture the reference, you will have to add __block:

__block NSInteger count = 0;
id x = DEFER({
    NSLog(@"Defer: %@", @(count));
});
count = 123;
NSLog(@"Before defer block!");

Before defer block!
Defer: 123

See Blocks and Variables

Upvotes: 1

Related Questions