I'm using NSInvocation
to get some method returns, and unfortunately I seem to have a leak, but can't figure out how to free the void*
I'm allocating, after I've returned it from NSInvocation
.
In the following implementation I tried to free it with a block that gets performed on the next run loop, but I get a crash due to returnBuffer
not being allocated.
Why can't I free returnBuffer
in the block, and if it hasn't been allocated why is it getting through returnBuffer!=NULL
?
This is a special method that has to do with IMP
swizzling so I DON'T know the method return type. Putting it in NSData
or something will not work.
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
void* returnBuffer = (void *)malloc(length);
[invocation getReturnValue:&returnBuffer];
if(returnBuffer!=NULL){
void(^delayedFree)(void) = ^{ free(returnBuffer); };
[[NSOperationQueue mainQueue] addOperationWithBlock:delayedFree];
}
return returnBuffer;
}
return nil;
ANSWER
Got it to work the following way thanks to Josh's -[NSMutableData mutableBytes]
trick
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
NSMutableData * dat = [[NSMutableData alloc] initWithLength:length];
void* returnBuffer = [dat mutableBytes];
[invocation getReturnValue:&returnBuffer];
void(^delayedFree)(void) = ^{ [dat release]; };
[[NSOperationQueue mainQueue] addOperationWithBlock:delayedFree];
return returnBuffer;
}
return nil;
You can get a void *
from NSMutableData
, just like you can from malloc()
, and essentially turn it into an instance variable, so that the lifetime of the allocation is tied to the lifetime of the instance. I got this trick from Mike Ash. I've been doing some NSInvocation
monkeying myself lately; this is in a category on NSInvocation
(you should use your own prefix for the method, of course):
static char allocations_key;
- (void *) Wool_allocate: (size_t)size {
NSMutableArray * allocations = objc_getAssociatedObject(self,
&allocations_key);
if( !allocations ){
allocations = [NSMutableArray array];
objc_setAssociatedObject(self, &allocations_key,
allocations, OBJC_ASSOCIATION_RETAIN);
}
NSMutableData * dat = [NSMutableData dataWithLength: size];
[allocations addObject:dat];
return [dat mutableBytes];
}
I'm not sure where the code you've posted is located; if you're in your own custom class, you don't need to deal with the associated objects bits -- just make allocations
an ivar.
Tying this into your code:
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
void* returnBuffer = [self Wool_allocate:length];
[invocation getReturnValue:&returnBuffer];
return returnBuffer;
}
return nil;
If you use the associated object route, the allocations
array will be deallocated when this instance is. Otherwise, just put [allocations release]
into your dealloc
.
Also, to answer your question as posed, rather than just solving the problem: free()
won't operate on any pointer that you didn't get from malloc()
. When the pointer gets used in the Block, I'm pretty sure it gets copied, so you end up with another pointer -- still pointing to the same memory, but one that free()
doesn't think it owns. Thus, you get the error about not having allocated it.