【问题标题】:ARC: find memory leak calling drawInRect through a blockARC:通过块调用drawInRect找到内存泄漏
【发布时间】:2015-01-24 21:56:16
【问题描述】:

我是 Instruments 的新手,但我之前已经成功地发现了漏洞。这一次,不是这样——每次我调用这段代码时都会有 34MB 的泄漏! 我尝试在下面发布所有相关代码,同时剪掉 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;
    }
}

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

【问题讨论】:

  • 这个问题是在真机还是模拟器上?模拟器往往存在真实设备上不存在的泄漏。在真机上测试。
  • 在 ipad air 上运行 iOS 8.1.2

标签: ios objective-c memory-leaks


【解决方案1】:

想通了。从上面的代码可以看出,调用 PhotoManager:saveImage 的是一个辅助线程(实际上是一个 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];
}

【讨论】:

    【解决方案2】:

    改用 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);
    

    【讨论】:

    • 不,这对释放内存的时间没有任何影响。
    • 因为上面的代码在block里面,所以[image drawInRect:imageRect]在block完成后会自动释放,需要时间。但是上面的代码马上就会被释放出来,并且在一定时间内对内存的消耗有很大的不同。但正如你所说,最终它们是相同的。
    猜你喜欢
    • 2013-08-19
    • 2012-04-03
    • 2012-01-04
    • 2012-09-14
    • 2012-10-05
    • 1970-01-01
    • 2015-02-17
    • 2012-03-13
    • 2017-10-03
    相关资源
    最近更新 更多