Nullified Construction

.nil?

Memory Management Issues With GCD

I have been using GCD extensively on my desktop application, and I have noticed severe memory management issue. I have realised a strange memory usage increase when the application was ran overnight without any user interaction. The memory usage of the application was about 70MB before I went to bed. When I got up, the memory usage was on 400MB. This was unexpected, because I was always making sure that there are no memory leaks in my application by profiling with Instruments.

I was trying to come up with a valid explanation for the issue to fix the bug. I have profiled the objects in the memory using the command line tool “heap”, which was introduced on Snow Leopard. However, I could not find significant issues after running it for an hour.

I could not figure out what was wrong for the entire day, so I restarted the program before I went to bed last night, and profiled the objects in the memory. When I got up this morning, the memory usage was on 400MB as expected. I ran the profiler again, and it told me that there were insane amount of objects that were not being freed. This was unusual, because Instruments told me that there were no leaks. I have fiddled with the application for a while, and ran the profiler again. I was extremely surprised when I saw the new report generated by the heap tool. All objects that were not being freed were all gone! At this point, I’ve realised that this was not happening to the build that did not have GCD optimisation.

After a while, I found out that it was indeed caused by GCD optimisation code I wrote. I ran some tests, and realised that objects that were allocated in Blocks dispatched to main queue are not being released UNTIL something triggers the NSAutoreleasePool to drain. This means the auto-released objects allocated inside a Block that was dispatched to the main queue are not released until an user interaction occurs.

1
2
3
4
5
6
7
8
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // do something expensive
    dispatch_async(dispatch_get_main_queue(), ^{
        // All auto-released objects allocated
        // in here are not released until the
        // next event loop fires.
    });
});

The above code should explain what was going on. I ran several tests to validate that my hypothesis was correct. To fix the problem, I have changed the above code to the following code.

1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // do something expensive
    dispatch_async(dispatch_get_main_queue(), ^{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        // all auto-released objects are released when
        // this block finishes!
        [pool drain];
    });
});

After placing the NSAutoreleasePool inside a block dispatched to the main queue, everything was back to normal.