【问题标题】:Objective C Block Not Equal to Its Own CopyObjective C 块不等于它自己的副本
【发布时间】:2014-04-29 21:56:15
【问题描述】:

在我的应用程序中,我将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: 被调用时,块(有时)没有被删除。为什么?

【问题讨论】:

  • 出于好奇,您为什么要保存块的副本?在示例中,您没有将其用于任何用途。

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


【解决方案1】:

这里的问题是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。当计时器触发时,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];
}

【讨论】:

  • ASAIK 您将在 B 中拥有NSStackBlockBC 是一样的。您需要调用copy 将块移动到堆并创建NSMallocBlock
【解决方案2】:

更清晰的方法是用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();
}

【讨论】:

    【解决方案3】:

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

    - (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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-22
      • 2012-06-25
      • 2015-03-26
      • 1970-01-01
      • 1970-01-01
      • 2015-01-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多