1

私は Mac OSX 用の画像処理アプリに取り組んでおり、大量のメモリ リークに問題があります (ARC を使用しています)。メイン ウィンドウには、ピクセルの値を変更して画像を更新するスライダーがあります。しかし、スライダーの値を変更すると、アプリはますます多くのメモリを割り当てます (数分間の「スライド」で最大 10 GB のメモリが割り当てられます!)。

アプリは、グレースケールの非常に大きな画像 (30 ~ 40 MB) で動作します。ピクセルを使用して 3 つの C 配列を作成し、それらを操作しましたが、呼び出しましfreeたが、解放されていないようです。

スライダーの値を変更すると、そのメソッドが起動します。

-(void)changeCurrentMinOrMax
{
    imageProcessQueue = dispatch_queue_create("rwt.tz", NULL);
    dispatch_async(imageProcessQueue, ^{
        // Change display range
        [self setDisplayRangeWithMin:_currentMin andMax:_currentMax];
        // Pack pixels into filtered raw image data
        [self packPixelsIntoFilteredImageData];
        // Create filtered image data
        [self createImage:_rawFilteredImageData];
    });
}

呼び出されたメソッドの実装は次のとおりです。

- (void)setDisplayRangeWithMin:(int)min andMax:(int)max
{
    // Calculate number of gray levels
    NSUInteger numberOfGrayLevels = (NSUInteger)pow(2, _bitsPerPixel);
    // Calculate display range
    int range = max - min;
    // Set treshold
    for (unsigned long i = 0; i < numberOFPixels; i++) {
        if (originalPixels[i] < min) {
            pixels[i] = min;
        } else if (originalPixels[i] > max) {
            pixels[i] = max;
        } else {
            pixels[i] = originalPixels[i];
        }
        // map it again into 0-65535 values of gray
        pixels[i] = (UInt16)((numberOfGrayLevels - ZERO_INDEX) * (float)((pixels[i] - min) / (float)range));
    }
}

- (void)packPixelsIntoFilteredImageData
{
    UInt8 *revertedImageDataArray = malloc(sizeOfBitmap);
    unsigned long j = 0;
    for (unsigned long i = 0; i < sizeOfBitmap; i += 2) {
        revertedImageDataArray[i] = (UInt8)((pixels[j] & 0xFF00) >> 8);
        revertedImageDataArray[i+1] = (UInt8)(pixels[j] & 0x00FF);
        j++;
    }

    // pack an array into NSData again
    _rawFilteredImageData = [NSData dataWithBytes:revertedImageDataArray
                                           length:sizeOfBitmap];
    free(revertedImageDataArray);
    revertedImageDataArray = NULL;
}

- (NSImage *)createImage:(NSData *)imgData
{
    _bitsPerComponent = [imgData length] / (_width * _height) * BYTE_SIZE;
    _bitsPerPixel = _bitsPerComponent;
    _bytesPerRow = _width * _bitsPerPixel / BYTE_SIZE;

    CGDataProviderRef provider =
    CGDataProviderCreateWithCFData((CFDataRef)CFBridgingRetain(imgData));
    // which colorspace is better?
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2);
    //CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGImageRef imageRef = CGImageCreate((size_t)_width,
                                        (size_t)_height,
                                        _bitsPerComponent,
                                        _bitsPerPixel,
                                        _bytesPerRow,
                                        colorSpace,
                                        kCGImageAlphaNone,
                                        provider,
                                        NULL,
                                        false,
                                        kCGRenderingIntentDefault);
    CGColorSpaceRelease(colorSpace);
    CGDataProviderRelease(provider);

    NSSize size = NSMakeSize((CGFloat) _width, (CGFloat)_height);

    dispatch_async(dispatch_get_main_queue(), ^{
        [self willChangeValueForKey:@"image"];
        _image = [[NSImage alloc] initWithCGImage:imageRef
                                             size:size];
        [self didChangeValueForKey:@"image"];
        CGImageRelease(imageRef);
    });
    return _image;
}

dealloc で C 配列を解放します。

- (void)dealloc
{
    // Free memory allocated for C arrays
    if (pixels) {
        free(pixels);
        pixels = NULL;
    }
    if (originalPixels){
        free(originalPixels);
        originalPixels = NULL;
    }
    if (imageDataArray8) {
        free(imageDataArray8);
        imageDataArray8 = NULL;
    }
    // Remove observers
    [self removeObserver:self forKeyPath:@"currentMax"];
    [self removeObserver:self forKeyPath:@"currentMin"];
}

他の場所で他の C 配列を使用する場合は、同じ方法でそれらを解放します。メモリが解放されないように見え (現在のイメージを閉じて新しいイメージを開いても)、dealloc が発生します。何が起こっているのか分かりますか?そのせいで髪をかきむしる!

4

1 に答える 1

2

次のようなコード行があります。

CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)CFBridgingRetain(imgData));

ただし、『 Transitioning to ARC Release Notes』の「 Managing Toll-Free Bridging」セクションによると、次のように述べられています。

__bridge_retainedまたはCFBridgingRetain、Objective-C ポインターを Core Foundation ポインターにキャストし、所有権をユーザーに譲渡します。CFReleaseまたは関連する関数を呼び出して、オブジェクトの所有権を放棄する責任があります。

ここCFBridgingRetainでは単に不要です。そして、CFDataRef渡した を決して解放していないため、リークしていますCGDataProviderCreateWithCFData(プロバイダーは解放しますが、 は解放しませんCFDataRef)。ARC に所有権を保持させ、クリーンアップしてもらうこともできます。CFDataRefこれは、(a) で所有権を譲渡した を追跡しCFBridgingRetain、次に (b) を手動でCFReleaseにコードを追加するよりも簡単ですCFDataRef。その行を次のように変更して、所有権をまったく譲渡しないようにするだけで、残りは ARC が処理します (NSDataつまり、 の場合)。

CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)imgData);

無関係ですが、createImageを返し_imageています。これは非同期に_image設定されているため、その行に到達するまでには設定されませんreturn。これを修正して適切な値を返すようにすることもできますが、 からの戻り値を使用していないことが判明したため、戻り値createImageの型を に変更する方が簡単な場合がありますvoid。しかし、現在のコードは少し紛らわしいだけで、実際には返していないのに何かを返していることを示唆しています。

于 2013-10-12T22:11:03.817 に答える