【问题标题】:NSView cacheDisplayInRect:toBitmapImageRep: Memory LeakNSView cacheDisplayInRect:toBitmapImageRep: 内存泄漏
【发布时间】:2026-02-24 01:45:01
【问题描述】:

在我的项目 (Cocoa) 中,我需要比较两个 NSView 的视觉相似度。下面是比较函数:

- (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);
            });
        });
    }
}

当我运行它时,我发现内存泄漏很大,可能超过 2GB。在 Xcode Instruments 中我发现这两行占用了大部分内存:

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

仪器中的屏幕截图:

我已经阅读了一些关于 SO 的类似问题,但似乎都没有帮助。


更新

Rob 建议我可能过于频繁地调用此函数。是的,我反复调用它,但我认为我不会在最后一次调用完成之前调用它。

这是我如何使用该功能:

- (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];
    }];
}

我没有在僵尸模式下运行它

【问题讨论】:

    标签: macos cocoa memory-leaks nsbitmapimagerep


    【解决方案1】:

    我可以证明此问题的唯一方法是重复调用此例程,而无需等待先前的调用完成。在那种情况下,我的增长曲线非常激进:

    但是,如果我给它一点喘息的空间(即,而不是不停地循环,我让例程 dispatch_async 在完成时调用自身,每个路标都指示对例程的单独调用),这很好:

    因此,要么您过于频繁地调用此例程,而没有给操作系统清理这些缓存的机会,要么您可能打开了僵尸程序或其他一些内存调试选项。但内存问题似乎并非来自此代码本身。

    如果您不是在没有暂停的情况下调用它,也没有打开任何这些内存调试选项,我可能会建议使用“调试内存图”(参见https://*.com/a/30993476/1271826)功能来识别任何强引用周期。此外,虽然这里似乎不太可能,但有时您会看到由于某些父对象正在泄漏而出现泄漏(因此请查找可能未释放的各种父类的实例,而不是关注正在泄漏的大项目)。

    【讨论】:

    • 嗨 Rob,请查看更新后的问题。我使用该功能的方式是否正确?请注意,我没有在僵尸模式下运行。
    • 或者,如果可能的话,你能告诉我你是如何“给它一点喘息的空间”(通过 GitHub gist 或其他方式)吗?
    • 我从NSTimer[NSThread sleepForTimeInterval] 做了一些“一点喘息空间”的演绎,但最终做了类似draw 方法的事情,你添加到修改后的问题中(即调用它来自完成处理程序)。如果您想格外小心,请暂时使用dispatch_after 而不是dispatch_async,以确保这不是时间问题。但是,最重要的是,我无法使用您的 draw 示例重现您的问题,并且在我们能够重现之前,我们无法提供真正的帮助。