Using @autoreleasepool to reduce your memory footprint

When I was reviewing iOS memory management in the Apple documentation, I came across the section Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint

With the introduction of ARC (Automatic Reference Counting) memory management is largely taken care of for you.   There are still some cases you need to worry about though, including an increase in your memory footprint caused by creating temporary objects in a loop.

Consider the following code that allows me to populate some test data into the iOS Address Book.   Note that I inserted a sleep(4) after each iteration to more clearly show the memory allocation picture in steady state before a new iteration begins.

// Populate some dummy entries in AddressBook for testing
+ (void)populateAddressBook
{

    // Set the seed for the random number generator for repeatable randomness.
    srandom(RANDOM_SEED_FOR_POPULATE);

    // Make several iterations of adding Address Book records and then saving.
    // We do this to keep peak memory allocations low.
    //
    for(NSInteger loopIndex =0; loopIndex < NUM_LOOPS_FOR_POPULATE; loopIndex ++){
        [self populateAddressBookForLoopIndex:loopIndex];

        // Sleep added for viewing purposes in Allocations Instrument
        sleep(4);
    }
}

// Handle one loop of populating Address Book entries.
// Called by populateAddressBook
+ (void) populateAddressBookForLoopIndex:(NSInteger)loopIndex
{
    CFErrorRef *error = NULL;
    ABAddressBookRef addressBook_cf = ABAddressBookCreate();

    // Get an array of records populated with test data
    CFArrayRef people_cf = [self newRandomPeopleWithCount:NUM_ENTRIES_TO_POPULATE_BEFORE_SAVE];

    CFIndex peopleArrCount = CFArrayGetCount(people_cf);

    ABRecordRef record_cf = NULL;

    for (int i=0; i
        record_cf = CFArrayGetValueAtIndex(people_cf, i);

        if(!ABAddressBookAddRecord(addressBook_cf, record_cf, error)){
            NSLog(@"Error trying to call ABAddressBookAddRecord");
        }
    }

    if(people_cf) {
        CFRelease(people_cf);
    }

    if(peopleArrCount > 0) {
        if(!ABAddressBookSave(addressBook_cf,error)) {
            NSLog(@"Error trying to call ABAddressBookSave");
        }
    }

    if(addressBook_cf){
        CFRelease(addressBook_cf);
    }
}

The memory allocation profile from running this loop 5 times looks like:

Address Book memory allocation without release pool

There is obviously a problem here.   The peak memory allocation that I measured toward the end was 35 MBytes – enough to possibly get my application automatically terminated by iOS 😦

In the documentation for autorelease pools they mention:

“Many programs create temporary objects that are autoreleased. These objects add to the program’s memory footprint until the end of the block.  …  you can create your own autorelease pool block. At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint.”

In my own experiments, the temporary objects added to the memory footprint until the end of the current event loop, not just the current block.   In any case, I created my own autorelease pool as suggested and modified the following code in the first method:


    for(NSInteger loopIndex =0; loopIndex < NUM_LOOPS_FOR_POPULATE; loopIndex ++){
        @autoreleasepool {
            [self populateAddressBookForLoopIndex:loopIndex];
        }

        sleep(4);
    }

}

Sure enough my memory profile changed dramatically:

Address Book memory allocation with release pool

My peak memory allocation usage is now 10.3 Mbytes.

Advertisements

5 responses to “Using @autoreleasepool to reduce your memory footprint

  1. Pingback: The development of Contacts2Web | Finalize.com: My journey with iOS and other code adventures

  2. Hi Scott, autoreleasepool does take care of temporary objects created in a loop or in a block of code. Surprisingly, the ABPersonCopyImageDataWithFormat with CFBridgingRelease to NSData wasn’t getting released and the CFData keeps growing !!! Any suggestions on how to solve this issue ???

    Like

  3. Hi Jenson – No suggestions offhand. Do you have a code example (Github, etc) that shows the issue?

    Like

    • Never mind that, a bit of debugging brought the culprit to the forefront. Addressbook was a global id and hence was creating an extra retain when any copy/create was being done in the local methods.

      Your blog is really informative and gets to the depth of most of the issues. I wish I had stumbled across your before I started coding in objective C.

      One more question, while creating a person (ABRecordRef) object and filling it with data for saving into the addressbook, I have noticed ABTokenListPopulateFromString caller , responsible library being Addressbook malloc objects which are retained/persistent. Any idea what the caller is doing, can’t find anything about the caller online ??!!

      Like

  4. Hi Jenson – Glad you enjoy my blog and thanks for the compliment! I did a search just now through my old project notes, the API docs, Google and Apple Developer forums. I did not see any good description of ABTokenListPopulateFromString. The only thing I noticed was a listing of the name in the context of a performance question on StackOverflow – http://stackoverflow.com/questions/12754683/abaddressbook-poor-performance-since-ios-6

    Sorry that I can’t be of more help here.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s