0
votes

I have recently been debugging a zombie issue with operations and found out that calling cancelAllOperations on the queue didn't cancel the operation in question, and in fact, the operation queue was empty even though the operation was still running.

The structure was a viewcontroller asynchronously loading a set of images off the web and perform some changes on them. Relevant (anonymised) exerpts follow:

@implementation MyViewController

- (id) init
{
    (...)
    mOperationQueue = [[NSOperationQueue alloc] init];
    (...)
}

- (void) viewDidAppear:(BOOL)animated
{
    (...)
    MyNSOperation * operation = [[MyNSOperation alloc] initWithDelegate:self andData:data];
    [mOperationQueue addOperation:operation];
    [operation release];
    (...)
}

- (void) dealloc
{
    (...)
    [mOperationQueue cancelAllOperations];
    [mOperationQueue release];
    (...)
}

- (void) imagesLoaded:(NSArray *)images
{
    (...)
}

And the operation in question:

@implementation MyNSOperation

- (id) initWithDelegate:(id)delegate andData:(NSDictionary *)data
{
    self = [super init];
    if (self)
    {
        mDelegate = delegate; // weak reference
        mData = [data retain];
        (...)
    }
    return self;
}

- (void) main
{
    NSAutoReleasePool * pool = [[NSAutoReleasePool alloc] init];

    mImages = [[NSMutableArray alloc] init];
    // load and compose images
    mAlteredImages = (...)

    [self performSelectorOnMainThread:@selector(operationCompleted) withObject:nil waitUntilDone:YES];

    [pool release];
}

- (void)operationCompleted
{
    if (![self isCancelled])
    {
        [mDelegate imagesLoaded:mAlteredImages];
    }
}

The observed flow is as follows:

  • The viewcontroller is shown, calling init and viewDidAppear starting the operation.
    • [mOperationQueue operations] contains exactly one element;
  • Shortly after, the operation enters main and
  • The viewcontroller is exited by the user before the operation completes.
  • dealloc is called on the viewcontroller (because the operation keeps a weak reference)
    • [mOperationQueue operations] contains zero (!) elements
  • cancelAllOperations is sent to the operation queue
    • [NSOperation cancel] is not called, resulting in an app-visible bogus state.
  • dealloc finishes
  • the operation completes
    • isCancelled returns false, resulting in a zombie call

The documentation of NSOperationQueue however explicitly states that "Operations remain queued until they finish their task." which looks like a breach of contract.

I've fixed the crash by keeping a reference to the operation and manually sending cancel, but I would like to know why the original approach isn't working to prevent further problems. Can someone shed some light on this?

Thanks in advance.

1
@Combuster...can u please share your working code for your question here - Priya

1 Answers

0
votes

cancelAllOperations does not cancel an already started operation. It only informs the operation about that fact and let the operation cancel themself, whenever it want. Thus, you can get a raise condition. Proceed with deallocacion, after you are sure that the operation is canceled.