Question

Consider a function like this one:

void* getData()
{
    void* data= malloc(32);
    NSData* __autoreleasing dataObject= [NSData dataWithBytesNoCopy: data length: 32 freeWhenDone: YES]
    return data;
}

If I try to execute this code and print the values (though they're not initialised, but just to make a test) in the area of memory returned, I don't get any exception.

But if I try to put a breakpoint and see the map of living objects, there isn't a NSData object alive, though data is still in the heap, why?

I would know how to return an autoreleased object, like before ARC. ARC handles everything, but in this case data is deallocated because I exit from the function scope. How to make it be alive and autoreleased after the call?

Was it helpful?

Solution

This is not possible. ARC was designed to automatically deallocate objects which are not accessible. Since the NSData object is local and not returned, it cannot be used, so ARC deallocates it.

Judging from the code you posted, it appears that you want to return a pointer to some data which will be automatically freed even though it is not an objective-c object. You may also want to be able to access this data using NSData APIs inside the function which generates it. You have a few options here.

  1. Change your code to return the NSData object. If the calling code wants direct access to the buffer it can using the NSData's bytes method. However, this will not work if your code is called from C.

  2. Stop trying to autorelease C data. Good C code should know when it needs to free data, so this should not be a problem. Simply change your code to freeWhenDone: NO and have the calling code use free() when it is done with the data.

  3. Place this function in its own file and disable ARC for that file. This allows you to do what you want by manually calling autorelease, but you will also have to manually handle the reference counting for the rest of the function.

  4. Combine 1 and 3. Have one function which uses ARC and returns the NSData object, and a wrapper function without ARC which calls the first to get an autoreleased object, then returns the buffer pointer from the result.

OTHER TIPS

One approach is to add a second function

NSData *autoreleaseBufferOfLength(void *bytes, NSUInteger length) 
{ 
    return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]; 
}  

This function will return an autoreleased NSData object.

Call this function from your buffer-returning function

void *getAutoreleasedBuffer(NSUInteger length)
{
    void *buffer = malloc(length);
    if (buffer) {
        autoreleaseBufferOfLength(buffer, length);
    }
    return buffer;
}

The NSData object returned from autoreleaseBufferOfLength is in the autorelease pool, so the buffer will be freed when the autorelease pool is drained (since the NSData object created in autoreleaseBufferOfLength is deallocated then).

I tested for debug this using this main function

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        __unused void * buffer = autoreleasingBuffer(32);

        NSLog(@"%s:exiting @autoreleasepool block", __PRETTY_FUNCTION__);
    }
    NSLog(@"%s:exited @autoreleasepool block", __PRETTY_FUNCTION__);
    return 0;
}

and by adding a symbolic breakpoint at -[NSConcreteData dealloc] with the action po @"deallocating NSConcreteData" and checking "Automatically continue after evaluating". This was the output

[53030:303] int main(int, const char **):exiting @autoreleasepool block
(NSString *) $0 = 0x0000000100300530 deallocating NSConcreteData
[53030:303] int main(int, const char **):exited @autoreleasepool block

I then tested for release, changing the test slightly, adding a __weak global variable g_data which is set in autoreleaseBufferOfLength,

__weak NSData *g_data = nil;
NSData *autoreleaseBufferOfLength(void *bytes, NSUInteger length) 
{ 
    return (g_data = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]); 
}  

and altering the logging in main:

int main(int argc, const char * argv[])
{
    @autoreleasepool 
    {
        __unused void * buffer = autoreleasingBuffer(32);

        NSLog(@"%s:exiting @autoreleasepool block; g_data = %@", __PRETTY_FUNCTION__, g_data);
    }
    NSLog(@"%s:exited @autoreleasepool block; g_data = %@", __PRETTY_FUNCTION__, g_data);
    return 0;
}

When building for release and running this was the output:

[53934:707] int main(int, const char **):exiting @autoreleasepool block; g_data = 00000000 00000070 00000000 00000070 10000000 00000000 00000000 00000000>
[53934:707] int main(int, const char **):exited @autoreleasepool block; g_data = (null)

These tests indicate that wrapping the call to dataWithBytesNoCopy:length:freeWhenDone: in a second function autoreleaseBufferOfLength has the effect of creating an NSData object and placing it in the autorelease pool.

Note: This is NOT a good idea. The best way to achieve a malloc'd which is free'd by the draining of the autorelease pool is to add a file which is built without ARC (using -fno-objc-arc), as @ughoavgfhw describes in his answer. Nevertheless, this approach may be of some interest.

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