Search code examples
iosmemorymemory-managementmemory-leakssskeychain

Why does this for loop bleed memory?


I am using ARC for my iOS project and am using a library called SSKeychain to access/save items to the keychain. I expect my app to access keychain items once every 10 seconds or so (to access API security token) at peak load and as such I wanted to test this library to see how it handles when called frequently. I made this loop to simulate an insane amount of calls and noticed that it bleeds a significant amount (~75 mb) of memory when run on an iPhone (not simulator):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSUInteger beginMemory = available_memory();
        for (int i = 0; i < 10000; ++i) {

            @autoreleasepool{
                NSError *  error2 = nil;
                SSKeychainQuery*  query2 = [[SSKeychainQuery alloc] init];
                query2.service = @"Eko";
                query2.account = @"loginPINForAccountID-2";
                query2.password = nil;
                [query2 fetch:&error2];
            }
        }
        NSUInteger endMemory = available_memory();

        NSLog(@"Started with %u, ended with %u, used %u", beginMemory, endMemory, endMemory-beginMemory);
    });

    return YES;
}

static NSUInteger available_memory(void) {
    // Requires #import <mach/mach.h>
    NSUInteger result = 0;
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size) == KERN_SUCCESS) {
        result = info.resident_size;
    }
    return result;
}

I am using SSKeychain which can be found here. This test bleeds about ~75 mb of memory regardless if things are actually stored on the keychain.

Any ideas what is happening? Is my testing methodology flawed?


Solution

  • I ran your code under the Leaks Instrument and this is what I saw from the Allocations track -

    Allocations Track

    Which is what you would expect - a lot of memory allocated during the loop and then it is released.

    Looking at the detail you see -

    Detail

    Persistent bytes on the heap of 2.36MB - This is the memory actually used by the app 'now' (i.e. after the loop with the app 'idling')

    Persistent objects of 8,646 - again, the number of objects allocated "now".

    Transient objects 663,288 - The total number of objects that have been created on the heap over the application lifetime. You can see from the difference between transient and persistent that most have been released.

    Total bytes of 58.70MB - This is the total amount of memory that has been allocated during execution. Not the total of memory in use, but the total of the amounts that have been allocated regardless of whether or not those allocations have been subsequently freed.

    The difference between the light and dark pink bar also shows the difference between the current 'active' memory use and the total use.

    You can also see from the Leak Checks track that there are no leaks detected.

    So, in summary, your code use a lot of transient memory as you would expect from a tight loop, but you wouldn't see this memory use in the normal course of your application execution where the keychain was accessed a few times every second or minute or whatever.

    Now, I would imagine that having gone to the effort of growing the heap to support all of those objects, iOS isn't going to release that now freed heap memory back to the system straight away; it is possible that your app may need a large heap space again later, which is why your code reports that a lot of memory is in use and why you should be wary of trying to build your own instrumentation rather than using the tools available.