macoscocoamemory-leaksnsbitmapimagerep

NSView cacheDisplayInRect:toBitmapImageRep: Memory Leak


In my project (Cocoa) I need to compare the visual similarity of two NSView. Here's the function for the comparison:

- (void)compareWithHandler: (void (^)(CGFloat fitness)) handler {
    @autoreleasepool {
        __block CGFloat fitness = 0;
        __block NSInteger count = 0;

        NSBitmapImageRep *bitmap1 = [_lennaImgView bitmapImageRepForCachingDisplayInRect:[_lennaImgView bounds]];
        NSBitmapImageRep *bitmap2 = [backgroundView bitmapImageRepForCachingDisplayInRect:[backgroundView bounds]];

        [_lennaImgView cacheDisplayInRect:_lennaImgView.bounds toBitmapImageRep:bitmap1];
        [backgroundView cacheDisplayInRect:backgroundView.bounds toBitmapImageRep:bitmap2];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSInteger w = [bitmap1 pixelsWide];
            NSInteger h = [bitmap1 pixelsHigh];
            NSInteger rowBytes = [bitmap1 bytesPerRow];
            unsigned char* pixels1 = [bitmap1 bitmapData];
            unsigned char* pixels2 = [bitmap2 bitmapData];

            int row, col;
            for (row = 0; row < h; row++){
                @autoreleasepool {
                    unsigned char* rowStart1 = (unsigned char*)(pixels1 + (row * rowBytes));
                    unsigned char* rowStart2 = (unsigned char*)(pixels2 + (row * rowBytes));
                    unsigned char* nextChannel1 = rowStart1;
                    unsigned char* nextChannel2 = rowStart2;
                    for (col = 0; col < w; col++){
                        unsigned char r1, g1, b1, a1, r2, g2, b2, a2;

                        r1 = *nextChannel1;
                        nextChannel1++;
                        g1 = *nextChannel1;
                        nextChannel1++;
                        b1 = *nextChannel1;
                        nextChannel1++;
                        a1 = *nextChannel1;
                        nextChannel1++;
                        r2 = *nextChannel2;
                        nextChannel2++;
                        g2 = *nextChannel2;
                        nextChannel2++;
                        b2 = *nextChannel2;
                        nextChannel2++;
                        a2 = *nextChannel2;
                        nextChannel2++;

                        unsigned char ary[] = {r1, r2, g1, g2, b1, b2};
                        double dist = vectorDistance(ary);
                        fitness += (CGFloat)map(dist, 0, 442, 1, 0);
                        count ++;
                    }
                }
            }
            fitness /= count;

            dispatch_async(dispatch_get_main_queue(), ^{
                handler(fitness);
            });
        });
    }
}

When I run it I noticed huge memory leak, which can be over 2GB. In Xcode Instruments I found that these two lines take up most memory:

    NSBitmapImageRep *bitmap1 = [_lennaImgView bitmapImageRepForCachingDisplayInRect:[_lennaImgView bounds]];
    NSBitmapImageRep *bitmap2 = [backgroundView bitmapImageRepForCachingDisplayInRect:[backgroundView bounds]];

Screenshot in Instrument: enter image description here

I've read some similar questions on SO but none of them seem to help.


Update

Rob suggest that I might be calling this function too frequently. Yes I am calling this repeatedly but I don't think I am calling it before the last call finished.

Here's how I use the function:

- (void)draw {      
    // Here I make some changes to the two NSView
    // Blablabla

    // Call the function
    [self compareWithHandler:^(CGFloat fitness) {
        // Use the fitness value to determine how we are going to change the two views

        [self draw];
    }];
}

And I am not running this with zombie mode on


Solution

  • The only way I could manifest this problem was to call this routine repeatedly without waiting for the prior call to finish. In that case, I had a pretty radical growth curve:

    enter image description here

    But if I gave this a bit of breathing room (i.e. rather than looping without stopping, I had the routine dispatch_async a call to itself upon completion, each signpost indicating a separate call to the routine), it was fine:

    enter image description here

    So either you are calling this routine too frequently and not giving the OS a chance to clean up these caches or maybe you have zombies or some other memory debugging option turned on. But the memory problem doesn't appear to be coming from this code, itself.

    If you aren't calling this without pause nor do you have any of those memory debugging options turned on, I might suggest using the "debug memory graph" (see https://stackoverflow.com/a/30993476/1271826) feature to identify any strong reference cycles. Also, while it doesn't seem likely here, sometimes you see leaks manifested because some parent object is leaking (so look for instances of your various parent classes that might not being released rather than focusing on the big items that are leaking).