ARC:通过块调用 drawInRect 查找内存泄漏

ARC: find memory leak calling drawInRect through a block

我是 Instruments 的新手,但我之前已经成功地找到了漏洞。这一次,不是这样——每次我调用这段代码时都会有 34MB 的泄漏! 我尝试 post 下面的所有相关代码,同时删除 DDLogging 等内容...

首先,显示问题的 Instruments 屏幕截图。请注意,我尝试模拟内存警告并等待一段时间,没有任何变化——此内存被永久占用。

PhotoManager.m:
- (void)saveImage:(UIImage*)unimage completionBlock:(void(^)(BOOL success, NSError *error))completionBlock {

    __weak typeof(self) weakSelf = self;

    dispatch_block_t work = ^{
        __block UIImage* image = [[AirprintCollagePrinter singleton] fixRotation:unimage];

        // if trial mode, always downsize and watermark image
        if( [PurchaseHelper singleton].getPurchaseLevel == PurchaseLevelTrial ) {

            dispatch_sync(_photoManagerQueue, ^{
                if( image.size.height > 1800 || image.size.width > 1200 ) {
                    image = [[AirprintCollagePrinter singleton] fitImage:image scaledToFillSize:CGSizeMake(1200, 1800)];
                }

                image = [weakSelf imageWithWatermark:image withWatermark:_watermarkImage];
            });
        }

        <snip>
    };

    if( [[NSThread currentThread] isMainThread] )
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), work);
    else
        work();
}

从 Instruments 屏幕截图中,您可以看到 AirprintCollagePrinter:fitImage 是跟踪中我的代码的最后一部分,所以这里是:

AirprintCollagePrinter.m:
- (UIImage *)fitImage:(UIImage *)image scaledToFillSize:(CGSize)size {
    // do not upscale

    @autoreleasepool {
        if( image.size.width <= size.width && image.size.height <= size.height )
            return image;

        CGFloat scale = MIN(size.width/image.size.width, size.height/image.size.height);
        CGFloat width = image.size.width * scale;
        CGFloat height = image.size.height * scale;
        CGRect imageRect = CGRectMake(0,
                                      0,
                                      width,
                                      height);

        UIGraphicsBeginImageContextWithOptions(size, YES, 0);
        [image drawInRect:imageRect];
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return newImage;
    }
}

...我记得结束图像上下文并将其包装在自动释放池中...我错过了什么?

想通了。从上面的代码来看,调用 PhotoManager:saveImage 的是辅助 thead(实际上是一个 NSOperation),它是这样做的:

while ( true ) {
    // do work
    [PhotoManager singleton] saveImage:xyz];

    [NSThread sleepForTimeInterval:0.5];
}

问题在于,虽然辅助线程的 main() 正在使用 @autoreleasepool,就像它应该的那样,该池永远没有时间耗尽,因为线程总是在休眠或工作。

解决方案是像这样使用另一个@autorelease 池,在它外面休眠:

while ( true ) {
    @autoreleasepool {
        // do work
        [PhotoManager singleton] saveImage:xyz];
    }
    [NSThread sleepForTimeInterval:0.5];
}

改为使用 CGImageRef。像这样:

CGImageRef imageRef = image.CGImage;
                CGColorSpaceRef colorspaceRef =CGColorSpaceCreateDeviceRGB();
                CFAutorelease(colorspaceRef);


                size_t width = itemSize.width;
                size_t height = itemSize.height;
                size_t bytesPerRow = kBytesPerPixel * width;

                CGContextRef context = CGBitmapContextCreate(NULL,
                                                             width,
                                                             height,
                                                             kBitsPerComponent,
                                                             bytesPerRow,
                                                             colorspaceRef,
                                                             kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);

                // Draw the image into the context and retrieve the new bitmap image without alpha
                CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
                CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
                image = [UIImage imageWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];

                CGContextRelease(context);
                CGImageRelease(imageRefWithoutAlpha);