UIImagePNGRepresentation 保存图片时Crash

最近遇到一个保存图片时的崩溃, 崩溃信息如下:

0  CoreFoundation!__exceptionPreprocess + 0x84
1  libobjc.A.dylib!objc_exception_throw + 0x38
2  CoreFoundation!+[NSException raise:format:] + 0x7c
3  Foundation!_NSMutableDataGrowBytes + 0x300
4  Foundation!-[NSConcreteMutableData appendBytes:length:] + 0x174
5  ImageIO!CGImageWriteSessionPutBytes + 0x94
6  ImageIO!write_fn + 0x28
7  ImageIO!png_write_chunk_data + 0x34
8  ImageIO!_cg_png_write_complete_chunk + 0x40
9  ImageIO!png_compress_IDAT + 0x160
10 ImageIO!png_write_find_filter + 0xa80
11 ImageIO!_cg_png_write_row + 0x300
12 ImageIO!writeOnePng + 0x1358
13 ImageIO!_CGImagePluginWritePNG + 0x90
14 ImageIO!CGImageDestinationFinalize + 0x1e8
15 UIKit!UIImagePNGRepresentation + 0x248
16 MFAFImageCache addImage:forRequest:withAdditionalIdentifier:

提示的异常信息是:

Exception Name: NSMallocException
Exception Reason: *** -[NSConcreteMutableData appendBytes:length:]: unable to allocate memory for length (1751849)

查看一下调用函数, 主要调用如下:

NSString *filePath = [FileModel getLocalFileName:kPhotosDirectory byUrl:[[request URL] absoluteString]];
 if (image) {
     @try {
         NSData *imageData = UIImagePNGRepresentation(image);
         [imageData writeToFile:filePath atomically:YES];
     } @catch (NSException *exception) {
         MFLogError(@"ImageCache", @"save image error:%@", exception);
     } @finally {
         
     }
 }

说明在执行 NSData *imageData = UIImagePNGRepresentation(image) 这个操作时,崩溃了。而且崩溃的原因是内存分配出错。 通常这种问题都是因为内存不足引起的。但是查看了一下log, 发现并没有 MemoryWarning 这句日志,说明程序还没来得及处理系统的内存警告就崩溃了

根据这些现象猜测可能的原因

  • 图片比较大
  • 外层也许有个for循环,多次调用,导致临时对象没来得及马上释放

针对这两种猜测,作了相应的处理

图片内容较大

其实有 UIImage 对象的时候,说明图片已经可以正常展示,加载这张图片到内存暂时是没有问题的。但保存的时候又根据 UIImage 对象生成 NSData, 然后在通过NSData保存文件。 在NSData写入文件到释放前,内存里面其实有两份 图片 的数据, 一份是显示的, 一份是准备用来保存的NSData。 这样其实就是临时分配了一块大空间,如果可以减少临时对象的分配,直接读取UIImage的数据来存储就可以降低内存的使用。
通过查阅文档,发现Image IO 的库可以做到。

于是,保存函数改成这样:

+ (BOOL)saveImage:(UIImage *)image toFile:(NSString *)filePath
{
    if (!image.CGImage) {
        return NO;
    }
    
    CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil);
    if (!destination) {
        return NO;
    }
    
    CGImageDestinationAddImage(destination, image.CGImage, nil);
    CGImageDestinationFinalize(destination);
    CFRelease(destination);
    
    return YES;
}

临时对象没有马上释放

这种情况比较好处理,就是因为加到 AutoReleasePool 的对象没有马上释放,必须要等下一次 RunLoop 循环才会清理。
这种问题可以加 @autoreleasepool{} 处理。
最后函数变成:

+ (BOOL)saveImage:(UIImage *)image toFile:(NSString *)filePath
{
    if (!image.CGImage) {
        return NO;
    }
    
    @autoreleasepool {
        CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath];
        CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil);
        if (!destination) {
            return NO;
        }
        
        CGImageDestinationAddImage(destination, image.CGImage, nil);
        CGImageDestinationFinalize(destination);
        CFRelease(destination);
    }
    
    return YES;
}

当然 @autoreleasepool 写在这里作用并不大, 应该要加在相应的for()循环里面。
因此定义一个宏,方便替换,只要把原来的 for 替换成 AutoRelease_for 就可以。

#define AutoRelease_for(...) for(__VA_ARGS__) @autoreleasepool 

你可能感兴趣的:(UIImagePNGRepresentation 保存图片时Crash)