Reputation: 2007
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 :(
Upvotes: 12
Views: 1709
Reputation: 7200
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
Upvotes: 0
Reputation: 6489
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
Upvotes: 8
Reputation: 52565
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?
Upvotes: 0
Reputation: 43452
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.
Upvotes: 1
Reputation: 6179
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.
Upvotes: 0