iOS即时通讯发送图片消息内存暴涨优化
问题:
即时通讯App在发送图片消息时内存暴涨导致网络请求初始化失败(内存不足OOM),发送消息失败。
可能原因分析:
- 有内存泄漏。
- 发送的图片消息,可能包含大图,没有进行压缩处理,导致内存占用过高。
- 在发送过程中,可能同时进行了图片的读取和处理,如果图片很大,处理过程中会产生很大的内存峰值。
解决方案:
- 对于发送消息时的内存暴涨:
- 检查是否有内存泄漏,使用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. 主线程更新UIdispatch_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:nilcompleted:^(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:imageURLplaceholderImage:niloptions:SDWebImageAvoidDecodeImage | SDWebImageScaleDownLargeImages |SDWebImageProgressiveLoadcompleted: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;