iOS即时通讯发送图片消息内存暴涨优化

问题:

即时通讯App在发送图片消息时内存暴涨导致网络请求初始化失败(内存不足OOM),发送消息失败。

可能原因分析:

  1. 有内存泄漏。
  2. 发送的图片消息,可能包含大图,没有进行压缩处理,导致内存占用过高。
  3. 在发送过程中,可能同时进行了图片的读取和处理,如果图片很大,处理过程中会产生很大的内存峰值。

解决方案:

  1. 对于发送消息时的内存暴涨:
  • 检查是否有内存泄漏,使用Instruments工具检测。

检测发现确实有内存泄漏,解决后发现问题还是存在。

  • 在发送图片消息前,对图片进行压缩(包括压缩质量和尺寸),然后再发送压缩后的图片。

  • 避免直接操作大图,可以使用后台线程进行处理,防止阻塞主线程。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1. 读取图片(避免使用imageNamed:)
    UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];

    复制代码
      // 2. 尺寸压缩(限制最大边长为1024)
      CGFloat maxSize = 1024.0;
      CGSize scaledSize = [self scaledSizeForImage:originalImage maxLength:maxSize];
      
      // 3. 质量压缩(70%质量)
      UIImage *compressedImage = [self resizeImage:originalImage toSize:scaledSize];
      NSData *imageData = UIImageJPEGRepresentation(compressedImage, 0.7);
      
      // 4. 发送压缩后的数据(非原始图片)
      [self sendImageData:imageData];

    });

    // 计算缩放尺寸

    • (CGSize)scaledSizeForImage:(UIImage *)image maxLength:(CGFloat)maxLength {
      CGFloat ratio = MIN(maxLength / image.size.width, maxLength / image.size.height);
      return CGSizeMake(image.size.width * ratio, image.size.height * ratio);
      }

    // 图片重绘

    • (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)targetSize {
      UIGraphicsBeginImageContextWithOptions(targetSize, NO, UIScreen.mainScreen.scale);
      [image drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
      UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      return resizedImage;
      }
  • 压缩后的图片数据要及时释放不必要的资源,避免在内存中同时存在多张图片,比如在图片展示后,如果原图不再需要,可以将其置为nil,帮助内存回收。(@autoreleasepool)。

处理到此内存暴涨解决了,但是随着发送图片内存还是在持续增加,现在每发送一张图片内存还是要涨10M。(message.compressRatio = 1.0 // 设置压缩率),message.compressRatio = 0.2,每发送一张图片内存也要涨5M。

新问题:

即时通讯App在发送图片消息时每次展示一张图片内存涨10M多。

可能原因分析:

1.图片加载方法不对

[UIImage imageNamed:]的内存缓存特性:

  • 系统级缓存无法自动释放

  • 特别不适合大图和列表展示场景

  • 会自动缓存图片到系统缓存

  • 适合重复使用的小图标

  • 不适合大图或单次使用的图片

2.内存增长原因:

为了支持 GIF/WebP 等动图格式,showImageView为SDAnimatedImageView,它解码后的帧缓存会增加内存占用。

  • 大图被缓存且无法及时释放

  • 图片解码后的位图数据占用内存

3.Cell 复用机制

快速滑动时可能同时加载多张大图,旧图片未及时释放

优化方案:

通过以下优化措施,图片展示内存问题应该能得到显著改善。核心要点是:

  • 避免使用 imageNamed: 加载大图

  • 合理配置 SDAnimatedImageView

  • 完善 cell 复用机制

  • 使用图片下采样技术

  • 滑动时优化资源使用

1.使用正确的图片加载方式,用 [UIImage imageWithContentsOfFile:filePath]替代[UIImage imageNamed:filePath]

优点:

  • 不会缓存图片

  • 适合大图和单次使用的图片

后台线程解码 + 尺寸适配

复制代码
// 后台线程处理图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @autoreleasepool {
        // 1. 从文件加载
        UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
        
        // 2. 压缩图片尺寸 (按需)
        CGSize targetSize = CGSizeMake(800, 800); // 根据需求调整
        UIGraphicsBeginImageContextWithOptions(targetSize, NO, [UIScreen mainScreen].scale);
        [originalImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
        UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        // 3. 主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.showImageView.image = scaledImage;
        });
    }
});

2.使用 ImageIO 框架高效加载

复制代码
#import <ImageIO/ImageIO.h>

NSURL *imageURL = [NSURL fileURLWithPath:filePath];
NSDictionary *options = @{(id)kCGImageSourceShouldCache: @NO}; // 禁用解码缓存
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CFRelease(source);

self.showImageView.image = image;

3.使用第三方图片加载库

复制代码
// 使用SDWebImage示例
#import <SDWebImage/SDWebImage.h>

[self.showImageView sd_setImageWithURL:[NSURL fileURLWithPath:filePath]placeholderImage:nil
                           completed:^(UIImage *image, NSError *error,
                           SDImageCacheType cacheType, 
                           NSURL *imageURL) {
                               // 加载完成回调
 }];

4.优化 SDAnimatedImageView 配置

复制代码
- (SDAnimatedImageView *)showImageView {
    if (!_showImageView) {
        _showImageView = [[SDAnimatedImageView alloc] init];
        _showImageView.contentMode = UIViewContentModeScaleAspectFill;
        _showImageView.userInteractionEnabled = YES;
        
        // 添加以下优化配置
        _showImageView.shouldIncrementalLoad = YES; // 渐进式加载
        _showImageView.maxBufferSize = 1024 * 1024; // 设置合理的缓冲区大小
        _showImageView.runLoopMode = NSDefaultRunLoopMode; // 滑动时暂停动画
    }
    return _showImageView;
}

5.Cell 复用时的内存管理

复制代码
// 在 cell 的 prepareForReuse 中清理
- (void)prepareForReuse {
    [super prepareForReuse];
    
    // 停止动画并释放资源
    [self.showImageView stopAnimating];
    self.showImageView.currentFrame = nil;
    self.showImageView.animationImages = nil;
    
    // 取消未完成的图片加载
    [self.showImageView sd_cancelCurrentImageLoad];
}

6.图片尺寸优化(针对大图)

复制代码
// 使用 ImageIO 进行下采样
- (UIImage *)downsampleImageAtPath:(NSString *)path toSize:(CGSize)size {
    NSURL *url = [NSURL fileURLWithPath:path];
    NSDictionary *options = @{
        (id)kCGImageSourceShouldCache: @NO,
        (id)kCGImageSourceShouldAllowFloat: @YES
    };
    CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
    
    CGFloat maxDimension = MAX(size.width, size.height) * [UIScreen mainScreen].scale;
    NSDictionary *downsampleOptions = @{
        (id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
        (id)kCGImageSourceShouldCacheImmediately: @YES,
        (id)kCGImageSourceThumbnailMaxPixelSize: @(maxDimension)
    };
    
    CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (CFDictionaryRef)downsampleOptions);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    
    if (imageRef) CFRelease(imageRef);
    if (source) CFRelease(source);

    
    return image;
}

7.滑动性能优化

复制代码
// 在 scrollView 代理中实现以下方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 暂停屏幕外 cell 的动画
    for (UITableViewCell *cell in self.tableView.visibleCells) {
        if ([cell isKindOfClass:[YourCellClass class]]) {
            YourCellClass *yourCell = (YourCellClass *)cell;
            [yourCell.showImageView startAnimating];
        }
    }
    
    // 暂停非可见 cell 的动画
    NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
    for (NSIndexPath *indexPath in self.loadedIndexPaths) {
        if (![visiblePaths containsObject:indexPath]) {
            YourCellClass *cell = (YourCellClass *)[self.tableView cellForRowAtIndexPath:indexPath];
            [cell.showImageView stopAnimating];
        }
    }
}

其他优化建议:

1.内存警告处理

复制代码
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // 清除所有图片缓存
    [[SDImageCache sharedImageCache] clearMemory];
}

2.使用合适的 SDWebImage 选项:

复制代码
[self.showImageView sd_setImageWithURL:imageURL
                    placeholderImage:nil
                           options:SDWebImageAvoidDecodeImage | 
                                   SDWebImageScaleDownLargeImages |
                                   SDWebImageProgressiveLoad
                          completed:nil];

3.监控内存使用:

复制代码
- (void)monitorMemoryUsage {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(),
                                 TASK_BASIC_INFO,
                                 (task_info_t)&info,
                                 &size);
    if (kerr == KERN_SUCCESS) {
        NSLog(@"Memory in use (in MB): %f", info.resident_size / 1024.0 / 1024.0);
    }
}

4.配置 SDWebImage 全局参数(AppDelegate 中)

复制代码
// 设置全局缓存策略
SDImageCacheConfig *cacheConfig = [SDImageCacheConfig defaultCacheConfig];
cacheConfig.maxMemoryCost = 100 * 1024 * 1024; // 100MB 内存缓存
cacheConfig.maxMemoryCount = 50; // 最大缓存图片数量
cacheConfig.shouldDecompressImages = NO; // 禁止自动解压
[SDImageCache sharedImageCache].config = cacheConfig;