I’ve been doing some lower level graphics stuff recently for my Photo Booth app and ran into some interesting memory leaks. I had a feeling something was off so ran Instruments after coding, and sure enough, leaks were detected. Several actually. The culprits:

CGPathRef

The biggest offender by far. Forgetting to call CGPathRelease(path) after any form of CGPathCreate…() was the most comment mistake. An interesting case also came up with holding onto an instance variable of the path. So I had this path that was expensive to calculate and gets reused, so I save it into an instance variable:

@property (nonatomic) CGPathRef scaledActivePath;

But when it does get recalculated, I want to release the previous saved path, so I override the setter:

- (void)setScaledActivePath:(CGPathRef)scaledActivePath {
    CGPathRelease(_scaledActivePath);
    
    _scaledActivePath = scaledActivePath;
}

And finally, it needs to be released when the view controller is finished with:

- (void)dealloc {
    CGPathRelease(self.scaledActivePath);
}

 

Image construction with malloc()

In this case, I’m creating a color picker by calculating r, g, b values and placing them into a UInt8 * array, which is then used to create a CGDataProviderRef and CGImageRef. 

The final result looked great:

screen-shot-2017-07-11-at-11-31-37-pm

The code involved looked like:

NSInteger width = self.shadePicker.frame.size.width;
NSInteger height = self.shadePicker.frame.size.height;
NSInteger dataLength = width * height * 4;
self.shadeData = (UInt8 *) malloc(dataLength * sizeof(UInt8));

for (int column = 0; column < width; column++) {
    for (int row = 0; row < height; row++) {
        long i = (column + row * width) * 4;
        
        CGFloat r;
        CGFloat g;
        CGFloat b;
        
        [self colorForBaseHue:hue
                       column:column row:row
                        width:width height:height
                            r:&r g:&g b:&b];
        
        self.shadeData[i] = r;
        self.shadeData[i+1] = g;
        self.shadeData[i+2] = b;
        self.shadeData[i+3] = 255.0;
    }
}

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, self.shadeData, dataLength, NULL);
CGImageRef imgRef = CGImageCreate(width, height, 8, 32, 4 * width, colorSpace,kCGImageByteOrder32Big | kCGImageAlphaPremultipliedLast, dataProvider, NULL, true, kCGRenderingIntentDefault);
UIImage *img = [UIImage imageWithCGImage:imgRef];

CGImageRelease(imgRef);
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(dataProvider);

Notice what’s missing? Yes! I was forgetting to free the shadeData array I had malloc’ed! However, to my surprise, calling  free(self.shadeData)immediately after the other releases actually corrupts the imageRef and UIImage generated. Rather than a glorious gradient, the image becomes a mess of white/blue pixels. My guess is that the shadeData content still gets referenced in the image, and cannot safely be discarded even after the image is constructed.

To get around this, you’re seeing some of the solution already. I save the shadeData reference into an instance variable, then release in the dealloc when the view controller is out of scope:

- (void)dealloc {
    free(self.shadeData);
}

 

Working through these leaks was actually a ton of fun. Every leak was a puzzle, it was super interesting to work through my code and fix it up. From now on, my goal is to run Allocations/Leak Detector after every screen is finished.

 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *