前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【IOS开发基础系列】SDWebImageDownloader专题

【IOS开发基础系列】SDWebImageDownloader专题

作者头像
江中散人_Jun
发布2023-10-16 11:55:02
6990
发布2023-10-16 11:55:02
举报
文章被收录于专栏:云原生布道专栏

1 机制原理

        SDWebImage是一个很厉害的图片缓存的框架。既ASIHttp+AsyncImage之后,我一直使用AFNetworking集成的UIImageView+AFNetworking.h,但后者对于图片的缓存实际应用的是NSURLCache自带的cache机制。而NSURLCache每次都要把缓存的raw  data 再转化为UIImage,就带来了数据处理和内存方面的更多操作。具体的比较在这里

        SDWebImage提供了如下三个category来进行缓存。

    • MKAnnotationView(WebCache)

    • UIButton(WebCache)

    • UIImageView(WebCache)

        以最为常用的UIImageView为例:

    1、UIImageView+WebCache: setImageWithURL: placeholderImage: options: 

    先显示 placeholderImage,同时由SDWebImageManager 根据 URL 来在本地查找图片。

    2、SDWebImageManager: downloadWithURL: delegate: options: userInfo:

    SDWebImageManager是将UIImageView+WebCache同SDImageCache链接起来的类,     SDImageCache:queryDiskCacheForKey:delegate:userInfo:

    用来从缓存根据CacheKey查找图片是否已经在缓存中

    3、如果内存中已经有图片缓存, SDWebImageManager会回调SDImageCacheDelegate: imageCache: didFindImage: forKey: userInfo:

    4、而 UIImageView+WebCache 则回调SDWebImageManagerDelegate:  webImageManager: didFinishWithImage: 来显示图片。

    5、如果内存中没有图片缓存,那么生成 NSInvocationOperation 添加到队列,从硬盘查找图片是否已被下载缓存。

    6、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。

    7、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。

    8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo:。

    9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

    10、图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

    11、connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

    12、connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

    13、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

    14、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder: didFinishDecodingImage: userInfo: 回调给 SDWebImageDownloader。

    15、imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

    16、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

    17、将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。

    18、写文件到硬盘在单独 NSInvocationOperation 中完成,避免拖慢主线程。

    19、如果是在iOS上运行,SDImageCache 在初始化的时候会注册notification 到 UIApplicationDidReceiveMemoryWarningNotification以及 UIApplicationWillTerminateNotification,在内存警告的时候清理内存图片缓存,应用结束的时候清理过期图片。

    20、SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

2 开发技巧

2.1 常见问题

2.1.1 下载大量图片导致内存告警
2.1.1.1 问题原因

    1、CGBitmapContextCreateImage绘制的图片会造成内存无法释放,应该换用CGDataProviderCreateWithCFData;

    2、加载大量图片时,SD会将图片进行解压(加快渲染速度,但是内存会增大差不多一倍),然后将解压后的Image数据缓存在内存中,从而导致内存暴涨;

以下代码具有内存泄露问题:

    // 原始方案

    UIGraphicsBeginImageContextWithOptions(imageSize,YES, 0);

    [image drawInRect: imageRect];

    UIImage *imgData = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return imgData;

//   改进方案1

//        CGImageRef imgRef =CGImageCreateWithImageInRect(image.CGImage,CGRectMake(0,0,image.size.width,image.size.height));

//   

//       UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);

//        CGContextRef context = UIGraphicsGetCurrentContext();

//        CGContextDrawImage(context, imageRect, imgRef);

//    //   [image drawInRect: imageRect];

//        UIImage *imgData = UIGraphicsGetImageFromCurrentImageContext();

//        UIGraphicsEndImageContext();

//        CGImageRelease(imgRef);

//        UIImage *data = [self verticallyFlipImage: imgData];

//       

//        return data;

    //方案二,内存有释放,挂机

//   UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);   

//    CGContextRef context =UIGraphicsGetCurrentContext();

//    CGRect rect = CGRectMake(0, 0,imageSize.width * [UIScreen mainScreen].scale, imageSize.height * [UIScreenmainScreen].scale);

//    // draw alpha-mask

    CGContextSetBlendMode(context,kCGBlendModeNormal);

//    CGContextDrawImage(context, rect,image.CGImage);

//    // draw tint color, preserving alpha valuesof original image

    CGContextSetBlendMode(context,kCGBlendModeSourceIn);

//

//    CGContextFillRect(context, rect);

//   

//    //Set the original greyscale template asthe overlay of the new image

//    UIImage *imgData = [selfverticallyFlipImage:image];

//    [imgData drawInRect:imageRect];

//    UIImage *colouredImage =UIGraphicsGetImageFromCurrentImageContext();

//    UIGraphicsEndImageContext();

//    colouredImage = [selfverticallyFlipImage:colouredImage];

//    CGContextRelease(context);

//   

//    return colouredImage;

    //方案三,内存没释放

//    CGFloat targetWidth = imageSize.width *[UIScreen mainScreen].scale;

//    CGFloat targetHeight = imageSize.height *[UIScreen mainScreen].scale;

//    CGImageRef imageRef = [image CGImage]; 

//    CGBitmapInfo bitmapInfo =CGImageGetBitmapInfo(imageRef);  

//    CGColorSpaceRef colorSpaceInfo =CGImageGetColorSpace(imageRef);

//    CGContextRef bitmapContext;

//    bitmapContext = CGBitmapContextCreate(NULL,targetWidth, targetHeight,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef),colorSpaceInfo, bitmapInfo);

//    CGContextDrawImage(bitmapContext,CGRectMake(0, 0, targetWidth, targetHeight), imageRef);

//    CGImageRef imgref =CGBitmapContextCreateImage(bitmapContext);

//    UIImage* newImage = [UIImage imageWithCGImage: imgref];

//    CGColorSpaceRelease(colorSpaceInfo);

//    CGContextRelease(bitmapContext);

//    CGImageRelease(imgref);

//   

//   return newImage;

2.1.1.2 方案一:修改源代码,入缓存前做数据压缩

http://my.oschina.net/u/1244672/blog/510379

        SDWebImage有一个SDWebImageDownloaderOperation类来执行下载操作的。里面有个下载完成的方法:

- (void) connectionDidFinishLoading: (NSURLConnection*)aConnection {

    SDWebImageDownloaderCompletedBlockcompletionBlock = self.completedBlock;

    @synchronized(self) {

        CFRunLoopStop(CFRunLoopGetCurrent());

        self.thread = nil;

        self.connection= nil;

        [[NSNotificationCenter defaultCenter] postNotificationName: SDWebImageDownloadStopNotification object: nil];

    }

    if(![[NSURLCache sharedURLCache] cachedResponseForRequest: _request]) {

        responseFromCached= NO;

    }

    if(completionBlock)

    {

        if(self.options & SDWebImageDownloaderIgnoreCachedResponse &&responseFromCached) {

            completionBlock(nil, nil, nil, YES);

        }

        else {

            UIImage *image= [UIImage sd_imageWithData: self.imageData];

            NSString *key= [[SDWebImageManager sharedManager] cacheKeyForURL: self.request.URL];

            image = [self scaledImageForKey: key image: image];

            // Do not force decoding animated GIFs

            if(!image.images) {

                image =[UIImage decodedImageWithImage: image];

            }

            if(CGSizeEqualToSize(image.size, CGSizeZero)) {

                completionBlock(nil, nil, [NSError errorWithDomain: @"SDWebImageErrorDomain" code: 0 userInfo: @{NSLocalizedDescriptionKey : @"Downloaded image has 0pixels"}], YES);

            }

            else {

                completionBlock(image, self.imageData, nil, YES);

            }

        }

    }

    self.completionBlock= nil;

    [self done];

}

其中,UIImage *image = [UIImage sd_imageWithData: self.imageData]; 就是将data转换成image。

再看看sd_imageWithData:这个方法:

+ (UIImage*) sd_imageWithData: (NSData *)data {

    UIImage *image;

    NSString *imageContentType = [NSData sd_contentTypeForImageData: data];

    if ([imageContentType isEqualToString: @"image/gif"]) {

        image =[UIImage sd_animatedGIFWithData: data];

    }

#ifdef SD_WEBP

    else if([imageContentType isEqualToString: @"image/webp"])

    {

        image =[UIImage sd_imageWithWebPData: data];

    }

#endif

    else {

        image = [[UIImage alloc] initWithData: data];

        UIImageOrientationorientation = [self sd_imageOrientationFromImageData: data];

        if(orientation != UIImageOrientationUp) {

            image =[UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: orientation];

        }

    }

    return image;

}

        这个方法在UIImage+MultiFormat里面,是UIImage的一个类别处理。这句话很重要image =[[UIImage alloc] initWithData:data]; SDWebImage把下载下来的data直接转成image,然后没做等比缩放直接存起来使用。所以,我们只需要在这边做处理即可:

        UIImage+MultiFormat添加一个方法:

+ (UIImage *) compressImageWith: (UIImage *)image

{

    float imageWidth = image.size.width;

    float imageHeight = image.size.height;

    float width =640;

    float height =image.size.height / (image.size.width/width);

    float widthScale = imageWidth / width;

    float heightScale = imageHeight / height;

    // 创建一个bitmap的context并把它设置成为当前正在使用的context

    UIGraphicsBeginImageContext(CGSizeMake(width, height));

    if (widthScale> heightScale) {

        [image drawInRect: CGRectMake(0, 0, imageWidth / heightScale , height)];

    }

    else {

        [image drawInRect: CGRectMake(0, 0, width , imageHeight / widthScale)];

    }

    // 从当前context中创建一个改变大小后的图片

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    // 使当前的context出堆栈

    UIGraphicsEndImageContext();

    return newImage;

}

        然后在image =[[UIImage alloc] initWithData: data];下面调用以下:

    if(data.length/1024 > 1024) {

        image = [self compressImageWith: image];

    }

        当data大于1M的时候做压缩处理。革命尚未成功,还需要一步处理。在SDWebImageDownloaderOperation的connectionDidFinishLoading方法里面的:

        UIImage *image= [UIImage sd_imageWithData: self.imageData];

//将等比压缩过的image在赋在转成data赋给self.imageData

NSData *data = UIImageJPEGRepresentation(image, 1);

self.imageData = [NSMutableData dataWithData: data];

2.1.1.3 方案二:设置全局缓存大小

http://www.myexception.cn/swift/2033029.html

    1、首先在appdelegate方法didFinishLaunchingWithOptions

SDImageCache.sharedImageCache().maxCacheSize=1024*1024*8设置一下最大的缓存大小。

    2、在appdelegate applicationDidReceiveMemoryWarning里加入

SDImageCache.sharedImageCache().clearMemory()

SDWebImageManager.sharedManager().cancelAll()

2.1.1.4 方案三:定时清理内存缓存

http://www.bubuko.com/infodetail-956863.html

        经过尝试,发现了一个最简单的完美解决该问题的方法

        在使用SDWebImage加载较多图片造成内存警告时,定期调用

 [[SDImageCache sharedImageCache] setValue: nil forKey: @"memCache"];

2.1.1.5 方案四(不推荐):修复SD库代码,不做解压,直接返回压缩的原图
2.1.1.6 方案五(推荐):使用CGDataProviderRef进行图形解压重绘

iOS开发中界面展示大图片时UIImage的性能有关问题

http://www.myexception.cn/operating-system/578931.html

#import "SDWebImageDecoder.h"

@implementation UIImage (ForceDecode)

+ (UIImage*) decodedImageWithImage: (UIImage*)image {

    if (image.images) {

       // Do not decode animated images

       return image;

    }

    //仅仅作为临时应付方案

    //    return image;

    UIImage *decompressedImage;

    @autoreleasepool{

        //核心代码,可以解决内存未释放问题

        NSData *data = UIImageJPEGRepresentation(image, 1);

        CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

        CGImageRefimageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO,

kCGRenderingIntentDefault);

    //    CGImageRef imageRef = image.CGImage;

    CGSizeimageSize = CGSizeMake(CGImageGetWidth(imageRef),CGImageGetHeight(imageRef));

    CGRect imageRect = (CGRect){.origin = CGPointZero, .size=imageSize};

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);

    intinfoMask = (bitmapInfo & kCGBitmapAlphaInfoMask);

    BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || infoMask ==kCGImageAlphaNoneSkipFirst || infoMask ==kCGImageAlphaNoneSkipLast);

    // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB.

    // https://developer.apple.com/library/mac/#qa/qa1037/_index.html

    if(infoMask == kCGImageAlphaNone&& CGColorSpaceGetNumberOfComponents(colorSpace)

> 1) {

        // Unset the old alpha info.

        bitmapInfo &= ~kCGBitmapAlphaInfoMask;

        // Set noneSkipFirst.

        bitmapInfo |= kCGImageAlphaNoneSkipFirst;

    }

     // Some PNGs tell us they have alpha but only 3 components. Odd.

    else if(!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace)

== 3) {

        // Unset the old alpha info.

        bitmapInfo &= ~kCGBitmapAlphaInfoMask;

        bitmapInfo |= kCGImageAlphaPremultipliedFirst;

    }

    // It calculates the bytes-per-row based on the bitsPerComponent and width arguments.

    CGContextRef context = CGBitmapContextCreate(NULL, imageSize.width, imageSize.height,  CGImageGetBitsPerComponent(imageRef), 0, colorSpace, bitmapInfo);

    CGColorSpaceRelease(colorSpace);

    // If failed, return undecompressed image

    if(!context) 

        return image;

    CGContextDrawImage(context,imageRect, imageRef);

    CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);

    CGContextRelease(context);

    decompressedImage = [UIImage imageWithCGImage: decompressedImageRef scale: image.scale orientation: image.imageOrientation];

    CGImageRelease(decompressedImageRef);

}

//    CVPixelBufferRef pixelBuffer;

//   CreateCGImageFromCVPixelBuffer(pixelBuffer,&decompressedImageRef);

//    CGImage *cgImage =CGBitmapContextCreateImage(context);

//    CFDataRef dataRef =CGDataProviderCopyData(CGImageGetDataProvider(cgImage));

//    CGImageRelease(cgImage);

//    image->imageRef = dataRef;

//    image->image =CFDataGetBytePtr(dataRef);

    return decompressedImage;

}

3 参考链接

(GOOD)iOS开发中界面展示大图片时UIImage的性能有关问题

http://www.myexception.cn/operating-system/578931.html

(Good)iPhone - UIImage Leak, CGBitmapContextCreateImage Leak

http://stackoverflow.com/questions/1427478/iphone-uiimage-leak-cgbitmapcontextcreateimage-leak

Another iPhone - CGBitmapContextCreateImage Leak

http://stackoverflow.com/questions/1434714/another-iphone-cgbitmapcontextcreateimage-leak

UIGraphicsBeginImageContext vs CGBitmapContextCreate

http://stackoverflow.com/questions/4683448/uigraphicsbeginimagecontext-vs-cgbitmapcontextcreate

iPhone - CGBitmapContextCreateImage Leak, Anyone else withthis problem?

http://stackoverflow.com/questions/1431566/iphone-cgbitmapcontextcreateimage-leak-anyone-else-with-this-problem

Build and Analyze false positive on leak detection?

http://stackoverflow.com/questions/8438249/build-and-analyze-false-positive-on-leak-detection

iPhone - Multiple CGBitmapContextCreateImage Calls -ObjectAlloc climbing

http://stackoverflow.com/questions/1436465/iphone-multiple-cgbitmapcontextcreateimage-calls-objectalloc-climbing

(Good)ios开发图片处理,内存泄露

http://www.oschina.net/question/736524_69802

主题: CGBitmapContextCreateImage(bitmap)内存泄露问题处理

http://www.cocoachina.com/bbs/read.php?tid=31835

iOS异步图片加载优化与常用开源库分析

http://luoyibu.com/2015/05/12/iOS异步图片加载优化与常用开源库分析/

主题:图片处理开源函数ImageProcessing  CGDataProviderCreateWithData Bug修复

http://www.cocoachina.com/bbs/read.php?tid=116149

CGDataProviderCreateWithData对内存数据的释放

http://www.taofengping.com/2012/11/04/cgdataprovidercreatewithdata_memory_release/#.VmpqgoSitZE

IOS7.x下UIGraphicsGetImageFromCurrentImageContext引发内存暴涨,导致应用被结束掉

http://blog.163.com/l1_jun/blog/static/1438638820155593641529/

在iOS中与CGContextRef的内存泄漏

http://www.itstrike.cn/Question/55b86ce7-dfba-4548-a103-22dc5317420a.html

Quartz 2D (ProgrammingWithQuartz) note

http://renxiangzyq.iteye.com/blog/1188025

使用AFNetworking,SDWebimage和OHHTTPStubs

http://blog.shiqichan.com/using-afnetworking-sdwebimage-and-ohhttpstubs/

SDWebImage缓存图片的机制(转)

http://blog.csdn.net/zhun36/article/details/8900327

近来一个swift项目用uicollectionview 用sdwebimage 加载图片,发生内存猛增,直接闪退的情况,简单说一下解决方案

http://www.myexception.cn/swift/2033029.html

关于SDWebImage加载高清图片导致app崩溃的问题

http://www.bubuko.com/infodetail-956863.html

SDWebImage加载大图导致的内存警告问题

http://blog.csdn.net/richer1997/article/details/43481959

解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题

http://my.oschina.net/u/1244672/blog/510379

使用SDWebImage加载大量图片后造成内存泄露的解决办法

http://www.bubuko.com/infodetail-985746.html

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 机制原理
  • 2 开发技巧
    • 2.1 常见问题
      • 2.1.1 下载大量图片导致内存告警
  • 3 参考链接
相关产品与服务
图片处理
图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档