iOS即時(shí)通訊發(fā)送圖片消息內(nèi)存暴漲優(yōu)化

問(wèn)題:

即時(shí)通訊App在發(fā)送圖片消息時(shí)內(nèi)存暴漲導(dǎo)致網(wǎng)絡(luò)請(qǐng)求初始化失?。▋?nèi)存不足OOM),發(fā)送消息失敗。

可能原因分析:

  1. 有內(nèi)存泄漏。
  2. 發(fā)送的圖片消息,可能包含大圖,沒(méi)有進(jìn)行壓縮處理,導(dǎo)致內(nèi)存占用過(guò)高。
  3. 在發(fā)送過(guò)程中,可能同時(shí)進(jìn)行了圖片的讀取和處理,如果圖片很大,處理過(guò)程中會(huì)產(chǎn)生很大的內(nèi)存峰值。

解決方案:

  1. 對(duì)于發(fā)送消息時(shí)的內(nèi)存暴漲:
  • 檢查是否有內(nèi)存泄漏,使用Instruments工具檢測(cè)。
Instruments工具檢測(cè)結(jié)果

檢測(cè)發(fā)現(xiàn)確實(shí)有內(nèi)存泄漏,解決后發(fā)現(xiàn)問(wèn)題還是存在。

  • 在發(fā)送圖片消息前,對(duì)圖片進(jìn)行壓縮(包括壓縮質(zhì)量和尺寸),然后再發(fā)送壓縮后的圖片。

  • 避免直接操作大圖,可以使用后臺(tái)線程進(jìn)行處理,防止阻塞主線程。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1. 讀取圖片(避免使用imageNamed:)
    UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
    
    // 2. 尺寸壓縮(限制最大邊長(zhǎng)為1024)
    CGFloat maxSize = 1024.0;
    CGSize scaledSize = [self scaledSizeForImage:originalImage maxLength:maxSize];
    
    // 3. 質(zhì)量壓縮(70%質(zhì)量)
    UIImage *compressedImage = [self resizeImage:originalImage toSize:scaledSize];
    NSData *imageData = UIImageJPEGRepresentation(compressedImage, 0.7);
    
    // 4. 發(fā)送壓縮后的數(shù)據(jù)(非原始圖片)
    [self sendImageData:imageData];
});

// 計(jì)算縮放尺寸
- (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;
}
  • 壓縮后的圖片數(shù)據(jù)要及時(shí)釋放不必要的資源,避免在內(nèi)存中同時(shí)存在多張圖片,比如在圖片展示后,如果原圖不再需要,可以將其置為nil,幫助內(nèi)存回收。(@autoreleasepool)。

處理到此內(nèi)存暴漲解決了,但是隨著發(fā)送圖片內(nèi)存還是在持續(xù)增加,現(xiàn)在每發(fā)送一張圖片內(nèi)存還是要漲10M。(message.compressRatio = 1.0 // 設(shè)置壓縮率),message.compressRatio = 0.2,每發(fā)送一張圖片內(nèi)存也要漲5M。

新問(wèn)題:

即時(shí)通訊App在發(fā)送圖片消息時(shí)每次展示一張圖片內(nèi)存漲10M多。

可能原因分析:

1.圖片加載方法不對(duì)

Cell圖片展示

[UIImage imageNamed:]的內(nèi)存緩存特性:

  • 系統(tǒng)級(jí)緩存無(wú)法自動(dòng)釋放

  • 特別不適合大圖和列表展示場(chǎng)景

  • 會(huì)自動(dòng)緩存圖片到系統(tǒng)緩存

  • 適合重復(fù)使用的小圖標(biāo)

  • 不適合大圖或單次使用的圖片

2.內(nèi)存增長(zhǎng)原因:

為了支持 GIF/WebP 等動(dòng)圖格式,showImageView為SDAnimatedImageView,它解碼后的幀緩存會(huì)增加內(nèi)存占用。

  • 大圖被緩存且無(wú)法及時(shí)釋放

  • 圖片解碼后的位圖數(shù)據(jù)占用內(nèi)存

3.Cell 復(fù)用機(jī)制

快速滑動(dòng)時(shí)可能同時(shí)加載多張大圖,舊圖片未及時(shí)釋放

優(yōu)化方案:

通過(guò)以下優(yōu)化措施,圖片展示內(nèi)存問(wèn)題應(yīng)該能得到顯著改善。核心要點(diǎn)是:

  • 避免使用 imageNamed: 加載大圖

  • 合理配置 SDAnimatedImageView

  • 完善 cell 復(fù)用機(jī)制

  • 使用圖片下采樣技術(shù)

  • 滑動(dòng)時(shí)優(yōu)化資源使用

1.使用正確的圖片加載方式,用 [UIImage imageWithContentsOfFile:filePath]替代[UIImage imageNamed:filePath]

優(yōu)點(diǎn):

  • 不會(huì)緩存圖片

  • 適合大圖和單次使用的圖片

后臺(tái)線程解碼 + 尺寸適配

// 后臺(tái)線程處理圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @autoreleasepool {
        // 1. 從文件加載
        UIImage *originalImage = [UIImage imageWithContentsOfFile:filePath];
        
        // 2. 壓縮圖片尺寸 (按需)
        CGSize targetSize = CGSizeMake(800, 800); // 根據(jù)需求調(diào)整
        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.使用第三方圖片加載庫(kù)

// 使用SDWebImage示例
#import <SDWebImage/SDWebImage.h>

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

4.優(yōu)化 SDAnimatedImageView 配置

- (SDAnimatedImageView *)showImageView {
    if (!_showImageView) {
        _showImageView = [[SDAnimatedImageView alloc] init];
        _showImageView.contentMode = UIViewContentModeScaleAspectFill;
        _showImageView.userInteractionEnabled = YES;
        
        // 添加以下優(yōu)化配置
        _showImageView.shouldIncrementalLoad = YES; // 漸進(jìn)式加載
        _showImageView.maxBufferSize = 1024 * 1024; // 設(shè)置合理的緩沖區(qū)大小
        _showImageView.runLoopMode = NSDefaultRunLoopMode; // 滑動(dòng)時(shí)暫停動(dòng)畫
    }
    return _showImageView;
}

5.Cell 復(fù)用時(shí)的內(nèi)存管理

// 在 cell 的 prepareForReuse 中清理
- (void)prepareForReuse {
    [super prepareForReuse];
    
    // 停止動(dòng)畫并釋放資源
    [self.showImageView stopAnimating];
    self.showImageView.currentFrame = nil;
    self.showImageView.animationImages = nil;
    
    // 取消未完成的圖片加載
    [self.showImageView sd_cancelCurrentImageLoad];
}

6.圖片尺寸優(yōu)化(針對(duì)大圖)

// 使用 ImageIO 進(jìn)行下采樣
- (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.滑動(dòng)性能優(yōu)化

// 在 scrollView 代理中實(shí)現(xiàn)以下方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 暫停屏幕外 cell 的動(dòng)畫
    for (UITableViewCell *cell in self.tableView.visibleCells) {
        if ([cell isKindOfClass:[YourCellClass class]]) {
            YourCellClass *yourCell = (YourCellClass *)cell;
            [yourCell.showImageView startAnimating];
        }
    }
    
    // 暫停非可見(jiàn) cell 的動(dòng)畫
    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];
        }
    }
}

其他優(yōu)化建議:

1.內(nèi)存警告處理

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // 清除所有圖片緩存
    [[SDImageCache sharedImageCache] clearMemory];
}

2.使用合適的 SDWebImage 選項(xiàng):

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

3.監(jiān)控內(nèi)存使用:

- (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 全局參數(shù)(AppDelegate 中)

// 設(shè)置全局緩存策略
SDImageCacheConfig *cacheConfig = [SDImageCacheConfig defaultCacheConfig];
cacheConfig.maxMemoryCost = 100 * 1024 * 1024; // 100MB 內(nèi)存緩存
cacheConfig.maxMemoryCount = 50; // 最大緩存圖片數(shù)量
cacheConfig.shouldDecompressImages = NO; // 禁止自動(dòng)解壓
[SDImageCache sharedImageCache].config = cacheConfig;
性能對(duì)比
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容