Pergunta

I got a crash due to deallocating of variable that holds reference to block being executed. Here is code example:

This is what is wrong now in release, in debug on same device runs ok, it must be run as add-hoc to crash.

- (void)test {
    _test = [self doLater:^{
      _count++;
       [self test];
   } :3];
}

This is defined in NSObject category:

- (DoLaterProcess *)doLater:(void (^)())method :(double)delay {
    return [[DoLaterProcess new] from:method :delay];
}

End implementation of used class:

@implementation DoLaterProcess {
    id _method;
    BOOL _stop;
}

- (void)methodToPerform:(void (^)())methodToInvoke {
    if (_stop)return;
    if (NSThread.isMainThread) methodToInvoke();
    else [self performSelectorOnMainThread:@selector(methodToPerform:)      withObject:methodToInvoke waitUntilDone:NO];
}

- (DoLaterProcess *)from:(void (^)())method:(NSTimeInterval)delay {
    [self performSelector:@selector(methodToPerform:) withObject:method afterDelay:delay];
    _method = method;
    return self;
}

- (void)stop {
    _stop = YES;
    [NSObject cancelPreviousPerformRequestsWithTarget:self     selector:@selector(methodToPerform:) object:_method];
}

@end

So I understand that the _test variable is deallocated and then probably also block while in it is deallocated? So is that why it crashes? But why doesn't it crash in debug, can I force somehow compiler to crash on this also in debug? Thank you.

Foi útil?

Solução

Blocks capture local state. In your case the block is capturing _count and self. In order to do that efficiently, when you create a block it initially lives on the stack, with the effect that it is safe to be used only for as long as that method doesn't return. So you can pass blocks downward but you can't keep them or pass them upward without moving them to the heap. You achieve that by copying them (and copy is defined to act as retain if the block is already on the heap, so you don't pay for over-copying).

In your case, the correct thing would be for doLater:: to copy the block (though, for the record, unnamed parameters are considered extremely poor practice).

I'm a bit confused as to why you both assign the method to an instance variable and schedule it for a later pass it forward, but the quickest fix would be:

- (DoLaterProcess *)from:(void (^)())method:(NSTimeInterval)delay {
    method = [method copy];
    [self performSelector:@selector(methodToPerform:) withObject:method afterDelay:delay];
    _method = method;
    return self;
}

As to why this appears to have become broken under 4.6: you were relying on undocumented behaviour (albeit undocumented behaviour that feels like it should be natural) so any change in toolset or OS (or indeed, no change whatsoever) is permitted to affect that.

(aside: you also seem to be reimplementing a lot of what is built into GCD; you could directly replace from:: with dispatch_after and methodToPerform: with dispatch_async, in both cases supplying dispatch_get_main_queue() as the queue).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top