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