Question

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;
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top