Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

-[NSOperationQueue operations] returns an empty array when it shouldn't?

I'm writing an application that does async loading of images onto the screen. I have it set up to be NOT concurrent (that is, it spawns a thread and executes them one at a time), so I've only overridden the [NSOperation main] function in my NSOperation subclass.

Anyway, so when I add all of these operations, I want to be able later to access the queued operations to change their priorities. Unfortunately, whenever I call -[NSOperationQueue operations], all I get back is an empty array. The best part is that after putting in some console print statements, threads are still in the queue and executing (indicated by prints) despite the array being empty!

What gives? I also took a look at theadcount just to make sure they're all not executing at once and that does not appear to be the case.

Any ideas? Pulling my hair out on this one.

EDIT: Also worth mentioning that the same code provides a full array when run in the simulator :(

like image 632
Jeffrey Forbes Avatar asked Oct 30 '08 01:10

Jeffrey Forbes


1 Answers

I stepped through -operations, and found that it's basically doing:

[self->data->lock lock];
NSString* copy = [[self->data->operations copy] autorelease];
[self->data->lock unlock];
return copy;

except, after calling -autorelease, the subsequent instructions overwrite the register containing the only pointer to the new copy of the operations queue. The caller then just gets a nil return value. The "data" field is an instance of an internal class named _NSOperationQueueData which has fields:

NSRecursiveLock* lock;
NSArray* operations;

My solution was to subclass and override -operations, following the same logic, but actually returning the array copy. I added some sanity checks to bail out if the internals of NSOperationQueue are not compatible with this fix. This reimplementation is only called if a call to [super operations] does in fact return nil.

This could break in future OS releases if Apple were to change the internal structure, yet somehow avoid actually fixing this bug.

#if TARGET_OS_IPHONE

#import <objc/runtime.h>

@interface _DLOperationQueueData : NSObject {
@public
    id lock; // <NSLocking>
    NSArray* operations;
}
@end
@implementation _DLOperationQueueData; @end

@interface _DLOperationQueueFix : NSObject {
@public
    _DLOperationQueueData* data;
}
@end
@implementation _DLOperationQueueFix; @end

#endif


@implementation DLOperationQueue

#if TARGET_OS_IPHONE

-(NSArray*) operations
{
    NSArray* operations = [super operations];
    if (operations != nil) {
        return operations;
    }

    _DLOperationQueueFix* fix = (_DLOperationQueueFix*) self;
    _DLOperationQueueData* data = fix->data;

    if (strcmp(class_getName([data class]), "_NSOperationQueueData") != 0) {
        // this hack knows only the structure of _NSOperationQueueData
        // anything else, bail
        return operations;
    }
    if ([data->lock conformsToProtocol: @protocol(NSLocking)] == NO) {
        return operations; // not a lock, bail
    }

    [data->lock lock];
    operations = [[data->operations copy] autorelease];
    [data->lock unlock];
    return operations; // you forgot something, Apple.
}

#endif

@end

The header file is:

@interface DLOperationQueue : NSOperationQueue {}
#if TARGET_OS_IPHONE
-(NSArray*) operations;
#endif
@end
like image 156
Dave Lee Avatar answered Oct 19 '22 10:10

Dave Lee