为什么 objective-c 的块不能动态捕获值?

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

我想在 objective-c 中实现 defer。这是我的代码:

/**
  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

实现简单,似乎易于使用。 但它有问题!

我感到沮丧的事情来了。

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

日志是:

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!

所以有没有人能告诉我为什么 count 仍然是 0 块?

您已将大部分工作隐藏到宏中(这不是一种好的编程风格!)并且您缺少基本点 - DEFER 立即调用该块,甚至在到达 count = 123 之前。

如果您不将 [DeferImpl_ alloc] 的结果赋值给任何变量,它将立即释放,并立即调用该块。

你现在的代码和直接写一样:

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

如果您将代码更改为:

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

您的结果将是:

Before defer block!
Defer: 0

现在,为什么该值仍然为零?因为块通过值 捕获变量 。要捕获引用,您必须添加 __block:

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

Before defer block!
Defer: 123

Blocks and Variables