我有一个将块作为参数的方法。该块需要扩充,然后作为参数传递给该块的库函数。一个例子:
typedef void (^eblock_t)(void);
void libraryFunction(eblock_t block);
- (void)myMethod:(eblock_t)block {
libraryFunction ( ^{
block();
NSLog(@"block executed"); // This is the augmentation of the block
} );
}
该示例非常简单,适用于直接的情况。我使用 GHUnit 将该示例改进为以下示例。这有点做作,但可以尽可能简洁地说明我的问题:
EBlock.h
typedef void (^eblock_t)(void);
@interface EBlock : NSObject {
eblock_t _block;
}
@property (nonatomic, readwrite, strong) eblock_t blockOption1;
@property (nonatomic, readwrite, strong) eblock_t blockOption2;
- (void)chooseBlock:(NSUInteger)option;
- (void)executeBlock;
@end
EBlock.m
#import "EBlock.h"
@implementation EBlock
- (void)chooseBlock:(NSUInteger)option {
if (1 == option) {
// This is a block wrapping a block to augment the block
// This is the source of problem with test_switchOption_1For2
_block = ^{
self.blockOption1();
NSLog(@"option1"); // This is the augmentation
};
} else {
// There is no block wrapping the block and thus no augmentation of the block
// There is no issue with test_switchOption_2For1
_block = self.blockOption2;
}
}
- (void)executeBlock { _block(); }
@end
Test_EBlock.h
@class EBlock;
@interface Test_EBlock : GHTestCase
@property (nonatomic, readonly) NSUInteger counter1;
@property (nonatomic, readonly) NSUInteger counter2;
- (void)incrementCounter1;
- (void)incrementCounter2;
@end
Test_EBlock.m
#import "Test_EBlock.h"
#import "EBlock.h"
@implementation Test_EBlock
- (void)incrementCounter1 { _counter1++; }
- (void)incrementCounter2 { _counter2++; }
- (void)setUp {
[super setUp];
_counter1 = _counter2 = 0u;
}
- (void)tearDown { [super tearDown]; }
- (void)test_option1 {
EBlock *foo = [[EBlock alloc] init];
foo.blockOption1 = ^{ [self incrementCounter1]; };
foo.blockOption2 = ^{ [self incrementCounter2]; };
[foo chooseBlock:1];
[foo executeBlock];
GHAssertEquals(self.counter1, 1u, nil);
GHAssertEquals(self.counter2, 0u, nil);
}
- (void)test_option2 {
EBlock *foo = [[EBlock alloc] init];
foo.blockOption1 = ^{ [self incrementCounter1]; };
foo.blockOption2 = ^{ [self incrementCounter2]; };
[foo chooseBlock:2];
[foo executeBlock];
GHAssertEquals(self.counter1, 0u, nil);
GHAssertEquals(self.counter2, 1u, nil);
}
- (void)test_switchOption_1For2 {
EBlock *foo = [[EBlock alloc] init];
foo.blockOption1 = ^{ [self incrementCounter1]; };
foo.blockOption2 = ^{ [self incrementCounter2]; };
[foo chooseBlock:1];
// switch what is done in the block
foo.blockOption1 = ^{ [self incrementCounter2]; };
[foo executeBlock];
GHAssertEquals(self.counter1, 1u, nil); // This fails
GHAssertEquals(self.counter2, 0u, nil); // This fails
}
- (void)test_switchOption_2For1 {
EBlock *foo = [[EBlock alloc] init];
foo.blockOption1 = ^{ [self incrementCounter1]; };
foo.blockOption2 = ^{ [self incrementCounter2]; };
[foo chooseBlock:2];
// switch what is done in the block
foo.blockOption2 = ^{ [self incrementCounter1]; };
[foo executeBlock];
GHAssertEquals(self.counter1, 0u, nil);
GHAssertEquals(self.counter2, 1u, nil);
}
讨论
测试:test_option1
, test_option2
, &test_switchOption_2For1
通过。
test_switchOption_1For2
失败是因为GHAssertEquals(self.counter1, 0u, nil);
和GHAssertEquals(self.counter2, 1u, nil);
这是因为正在执行的块self.blockOption1
实际上是[self incrementCounter2]
而不是[self incrementCounter1]
。这是因为在EBlock.m
chooseBlock
块包装中,块已复制self.blockOption1
,在评估时是[self incrementCounter2]
. 有没有更好的方法来增加块,所以块不必被包装?或者有没有办法不延迟评估self.blockOption1
所以它是[self incrementCounter1]
。