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 :(
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With