Objective C Block不等于自己的副本

时间:2014-04-29 21:56:16

标签: ios objective-c macos objective-c-blocks

在我的应用中,我将NSTimer与传递给方法的块相关联;该块也被添加到一个块数组中。当计时器触发时,将调用其关联的块,并应从阵列中删除。所以我的设置如下:

@interface MyObject : NSObject

@property(strong, nonatomic) NSMutableArray *allBlocks;

- (void)myMethodWithBlock:(void(^)(void))block;
- (void)timerFired:(NSTimer *)timer;

@end

@implementation MyObject

- (id)init
{
    self = [super init];
    if (self)
    {
        self.allBlocks = [NSMutableArray array];
    }
    return self;
}

- (void)myMethodWithBlock:(void(^)(void))block
{
    [NSTimer scheduledTimerWithTimeInterval:5.0f
                                     target:self
                                   selector:@selector(timerFired:)
                                   userInfo:block
                                    repeats:NO];
    [self.allBlocks addObject:block];
}

- (void)timerFired:(NSTimer *)timer
{
    void(^block)(void) = timer.userInfo;
    [self.allBlocks removeObject:block];
    block();
}

@end

我的问题是,当调用timerFired:时,(有时)不会删除块。为什么呢?

3 个答案:

答案 0 :(得分:3)

这里的问题是NSTimer复制了分配给userInfo的块,但传递给myMethodWithBlock:的块可能是NSStackBlock的一个实例,它是不等于其副本

让我们考虑三种情况,其中myObjectMyObject的一个实例:

// A
void(^myBlock)(void) = ^{
    NSLog(@"1");
};
[myObject myMethodWithBlock:myBlock];

// B
int one = 1;
void(^myBlock)(void) = ^{
    NSLog(@"%d", one);
};
[myObject myMethodWithBlock:myBlock];

// C
int one = 1;
[myObject myMethodWithBlock:^{
    NSLog(@"%d", one);
};];
  • A 中,该块不会从其上下文中捕获任何变量;该块将是NSGlobalBlock的一个实例,它在复制时只返回自身。
  • B 中,该块捕获变量one;它将是NSMallocBlock的一个实例,它在复制时也会返回。
  • C 中,该块会再次捕获变量one,但在传递给myMethodWithBlock:之前也未分配给变量。在这种情况下,该块是NSStackBlock的实例,在复制时返回NSMallocBlock的实例。

这样做的结果是,在情景C的情况下,NSStackBlock将添加到allBlocks,而NSMallocBlock将被分配给计时器userInfo {1}}。当计时器触发时,removeObject:不执行任何操作,因为分配给计时器的块不等于阵列中的任何块。

解决方案是在将块存储在数组中之前始终复制块。这样,相同的块实例将存储在数组中并分配给计时器:

- (void)myMethodWithBlock:(void(^)(void))block
{
    block = [block copy];
    [NSTimer scheduledTimerWithTimeInterval:5.0f
                                     target:self
                                   selector:@selector(timerFired:)
                                   userInfo:block
                                    repeats:NO];
    [self.allBlocks addObject:block];
}

答案 1 :(得分:1)

更明确的方法是使用isEqual:行为众所​​周知且更具可读性的内容标记块,如NSNumber ...

// keep state so these can be made unique
@property(nonatomic, assign) NSInteger blockIndex;
// change the blocks collection to record blocks' associations with numbers
@property(nonatomic, strong) NSMutableDictionary *allBlocks;

// in myMethod...
NSNumber *nextIndex = [NSNumber numberWithInt:++self.blockIndex];
self.allBlocks[nextIndex] = block;

// pass userInfo:nextIndex when you schedule the timer

现在,计时器上下文永远不会有块,复制或其他方式。然后,当计时器触发......

- (void)timerFired:(NSTimer *)timer {

    NSNumber *index = timer.userInfo;

    void(^block)(void) = self.allBlocks[index]; 
    [self.allBlocks removeObjectForKey:index];

    block();
}

答案 2 :(得分:0)

我认为将苹果与苹果进行比较是最安全的。

- (void)myMethodWithBlock:(void(^)(void))block
{
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f
                                                      target:self
                                                    selector:@selector(timerFired:)
                                                    userInfo:block
                                                     repeats:NO];
    [self.allBlocks addObject:timer.userInfo];
}

- (void)timerFired:(NSTimer *)timer
{
    [self.allBlocks removeObject:timer.userInfo];

    void(^block)(void) = timer.userInfo;
    block();
}