views:

759

answers:

5

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 :(

A: 

No idea why you are seeing this behaviour but as a pure workaround you could keep your own references to the individual operations as they are added into the queue.

Dave Verwer
+1  A: 

I just do not believe there is enough context here to say what is going on. Clearly something is wrong, but you do not say how you are limiting concurrency, how you are testing to see the objects are running, etc.

As for the simulator vs the iPhone, NSOperations can act quite differently between the two, since all Intel based Macs are multiprocessor, and no iPhones are. Depending on how you are attempting to limit the concurrency you might be in a situation where not being able to execute on a second core prevents stuff from running, etc. But without more details it is impossible to know.

Louis Gerbarg
A: 

I've seen similar behaviour in low memory situations. How much memory are you using? Are you correctly clearing out caches and other temporary data when you get a didReceiveMemoryWarning message?

Stephen Darlington
+8  A: 

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
Dave Lee
Did you report this at http://bugreport.apple.com and if so, what's the bug number?
Chris Hanson
I hadn't submitted one, but did now. Bug ID# 6643022.
Dave Lee
Thanks for this - very useful.
tomtaylor
A: 

I just ran into the same problem. Simpler code than I use on an OS X application, and yet [myoperationqueue operations] always returns nil. I was planning on using that to avoid duplicating the queries. This is on iPhone OS 2.2.1. Sure seems like a bug. Thanks for the code, I may use it, or just use my own mirror of the queue.

This is not on the simulator, and I confirm that i add 20 or exact same copies of the job, which all line up nicely and do the job 19 times too many!

It is really pretty simple code. I am not using hardly any memory - this is on launch of an app that has no ui yet.

--Tom

Tom Andersen