1 機(jī)制原理
????????SDWebImage是一個(gè)很厲害的圖片緩存的框架葫笼。既ASIHttp+AsyncImage之后祝闻,我一直使用AFNetworking集成的UIImageView+AFNetworking.h碳抄,但后者對(duì)于圖片的緩存實(shí)際應(yīng)用的是NSURLCache自帶的cache機(jī)制草添。而NSURLCache每次都要把緩存的raw ?data 再轉(zhuǎn)化為UIImage酪劫,就帶來(lái)了數(shù)據(jù)處理和內(nèi)存方面的更多操作。具體的比較在這里珊蟀。
????????SDWebImage提供了如下三個(gè)category來(lái)進(jìn)行緩存菊值。
??????MKAnnotationView(WebCache)
??????UIButton(WebCache)
????? UIImageView(WebCache)
????????以最為常用的UIImageView為例:
????1、UIImageView+WebCache: setImageWithURL: placeholderImage: options:?
????先顯示 placeholderImage,同時(shí)由SDWebImageManager 根據(jù) URL 來(lái)在本地查找圖片腻窒。
????2昵宇、SDWebImageManager:?downloadWithURL: delegate: options: userInfo:
????SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來(lái)的類, ????SDImageCache:queryDiskCacheForKey:delegate:userInfo:
????用來(lái)從緩存根據(jù)CacheKey查找圖片是否已經(jīng)在緩存中
????3儿子、如果內(nèi)存中已經(jīng)有圖片緩存瓦哎,?SDWebImageManager會(huì)回調(diào)SDImageCacheDelegate: imageCache: didFindImage: forKey: userInfo:
????4、而 UIImageView+WebCache 則回調(diào)SDWebImageManagerDelegate:? webImageManager: didFinishWithImage: 來(lái)顯示圖片典徊。
????5、如果內(nèi)存中沒(méi)有圖片緩存恩够,那么生成 NSInvocationOperation 添加到隊(duì)列卒落,從硬盤查找圖片是否已被下載緩存。
????6蜂桶、根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件儡毕。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào)?notifyDelegate:扑媚。
????7腰湾、如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過(guò)小疆股,會(huì)先清空內(nèi)存緩存)费坊。SDImageCacheDelegate 回調(diào)?imageCache:didFindImage:forKey:userInfo:。進(jìn)而回調(diào)展示圖片旬痹。
????8附井、如果從硬盤緩存目錄讀取不到圖片,說(shuō)明所有緩存都不存在該圖片两残,需要下載圖片永毅,回調(diào)imageCache:didNotFindImageForKey:userInfo:。
????9人弓、共享或重新生成一個(gè)下載器?SDWebImageDownloader?開始下載圖片沼死。
????10、圖片下載由 NSURLConnection 來(lái)做崔赌,實(shí)現(xiàn)相關(guān) delegate 來(lái)判斷圖片下載中意蛀、下載完成和下載失敗。
????11健芭、connection:didReceiveData:?中利用 ImageIO 做了按圖片下載進(jìn)度加載效果浸间。
????12、connectionDidFinishLoading:?數(shù)據(jù)下載完成后交給?SDWebImageDecoder?做圖片解碼處理吟榴。
????13魁蒜、圖片解碼處理在一個(gè) NSOperationQueue 完成,不會(huì)拖慢主線程 UI。如果有需要對(duì)下載的圖片進(jìn)行二次處理兜看,最好也在這里完成锥咸,效率會(huì)好很多。
????14细移、在主線程?notifyDelegateOnMainThreadWithInfo:?宣告解碼完成搏予,imageDecoder: didFinishDecodingImage: userInfo:?回調(diào)給 SDWebImageDownloader。
????15弧轧、imageDownloader:didFinishWithImage:?回調(diào)給 SDWebImageManager 告知圖片下載完成雪侥。
????16、通知所有的 downloadDelegates 下載完成精绎,回調(diào)給需要的地方展示圖片速缨。
????17、將圖片保存到 SDImageCache 中代乃,內(nèi)存緩存和硬盤緩存同時(shí)保存旬牲。
????18、寫文件到硬盤在單獨(dú) NSInvocationOperation 中完成搁吓,避免拖慢主線程原茅。
????19、如果是在iOS上運(yùn)行堕仔,SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)notification 到?UIApplicationDidReceiveMemoryWarningNotification以及?UIApplicationWillTerminateNotification,在內(nèi)存警告的時(shí)候清理內(nèi)存圖片緩存擂橘,應(yīng)用結(jié)束的時(shí)候清理過(guò)期圖片。
????20摩骨、SDWebImagePrefetcher?可以預(yù)先下載圖片贝室,方便后續(xù)使用。
2 開發(fā)技巧
2.1 常見問(wèn)題
2.1.1 下載大量圖片導(dǎo)致內(nèi)存告警
2.1.1.1 問(wèn)題原因
????1仿吞、CGBitmapContextCreateImage繪制的圖片會(huì)造成內(nèi)存無(wú)法釋放滑频,應(yīng)該換用CGDataProviderCreateWithCFData;
????2唤冈、加載大量圖片時(shí)峡迷,SD會(huì)將圖片進(jìn)行解壓(加快渲染速度,但是內(nèi)存會(huì)增大差不多一倍)你虹,然后將解壓后的Image數(shù)據(jù)緩存在內(nèi)存中绘搞,從而導(dǎo)致內(nèi)存暴漲;
以下代碼具有內(nèi)存泄露問(wèn)題:
??? // 原始方案
??? UIGraphicsBeginImageContextWithOptions(imageSize,YES, 0);
??? [image drawInRect: imageRect];
??? UIImage *imgData = UIGraphicsGetImageFromCurrentImageContext();
??? UIGraphicsEndImageContext();
??? return imgData;
//? ?改進(jìn)方案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;
??? //方案二傅物,內(nèi)存有釋放夯辖,掛機(jī)
//???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;
??? //方案三,內(nèi)存沒(méi)釋放
//??? 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 方案一:修改源代碼董饰,入緩存前做數(shù)據(jù)壓縮
http://my.oschina.net/u/1244672/blog/510379
????????SDWebImage有一個(gè)SDWebImageDownloaderOperation類來(lái)執(zhí)行下載操作的蒿褂。里面有個(gè)下載完成的方法:
- (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轉(zhuǎn)換成image。
再看看sd_imageWithData:這個(gè)方法:
+ (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;
}
????????這個(gè)方法在UIImage+MultiFormat里面啄栓,是UIImage的一個(gè)類別處理娄帖。這句話很重要image =[[UIImage alloc] initWithData:data]; SDWebImage把下載下來(lái)的data直接轉(zhuǎn)成image,然后沒(méi)做等比縮放直接存起來(lái)使用昙楚。所以近速,我們只需要在這邊做處理即可:
????????UIImage+MultiFormat添加一個(gè)方法:
+ (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;
????// 創(chuàng)建一個(gè)bitmap的context并把它設(shè)置成為當(dāng)前正在使用的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)];
????}
????// 從當(dāng)前context中創(chuàng)建一個(gè)改變大小后的圖片
????UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
????// 使當(dāng)前的context出堆棧
????UIGraphicsEndImageContext();
????return newImage;
}
????????然后在image =[[UIImage alloc] initWithData: data];下面調(diào)用以下:
????if(data.length/1024 > 1024) {
????????image = [self compressImageWith: image];
????}
????????當(dāng)data大于1M的時(shí)候做壓縮處理。革命尚未成功堪旧,還需要一步處理削葱。在SDWebImageDownloaderOperation的connectionDidFinishLoading方法里面的:
????????UIImage *image= [UIImage sd_imageWithData: self.imageData];
//將等比壓縮過(guò)的image在賦在轉(zhuǎn)成data賦給self.imageData
NSData *data = UIImageJPEGRepresentation(image, 1);
self.imageData = [NSMutableData dataWithData: data];
2.1.1.3 方案二:設(shè)置全局緩存大小
http://www.myexception.cn/swift/2033029.html
????1、首先在appdelegate方法didFinishLaunchingWithOptions
SDImageCache.sharedImageCache().maxCacheSize=1024*1024*8設(shè)置一下最大的緩存大小淳梦。
????2析砸、在appdelegate?applicationDidReceiveMemoryWarning里加入
SDImageCache.sharedImageCache().clearMemory()
SDWebImageManager.sharedManager().cancelAll()
2.1.1.4 方案三:定時(shí)清理內(nèi)存緩存
http://www.bubuko.com/infodetail-956863.html
????????經(jīng)過(guò)嘗試,發(fā)現(xiàn)了一個(gè)最簡(jiǎn)單的完美解決該問(wèn)題的方法
????????在使用SDWebImage加載較多圖片造成內(nèi)存警告時(shí)谭跨,定期調(diào)用
?[[SDImageCache sharedImageCache] setValue: nil forKey: @"memCache"];
2.1.1.5 方案四(不推薦):修復(fù)SD庫(kù)代碼干厚,不做解壓李滴,直接返回壓縮的原圖
2.1.1.6 方案五(推薦):使用CGDataProviderRef進(jìn)行圖形解壓重繪
iOS開發(fā)中界面展示大圖片時(shí)UIImage的性能有關(guān)問(wèn)題
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;
??? }
??? //僅僅作為臨時(shí)應(yīng)付方案
????//??? return image;
??? UIImage *decompressedImage;
????@autoreleasepool{
????????//核心代碼螃宙,可以解決內(nèi)存未釋放問(wèn)題
??????? 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開發(fā)中界面展示大圖片時(shí)UIImage的性能有關(guān)問(wèn)題
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?
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
(Good)ios開發(fā)圖片處理,內(nèi)存泄露
http://www.oschina.net/question/736524_69802
主題: CGBitmapContextCreateImage(bitmap)內(nèi)存泄露問(wèn)題處理
http://www.cocoachina.com/bbs/read.php?tid=31835
iOS異步圖片加載優(yōu)化與常用開源庫(kù)分析
http://luoyibu.com/2015/05/12/iOS異步圖片加載優(yōu)化與常用開源庫(kù)分析/
主題:圖片處理開源函數(shù)ImageProcessing? CGDataProviderCreateWithData Bug修復(fù)
http://www.cocoachina.com/bbs/read.php?tid=116149
CGDataProviderCreateWithData對(duì)內(nèi)存數(shù)據(jù)的釋放
http://www.taofengping.com/2012/11/04/cgdataprovidercreatewithdata_memory_release/#.VmpqgoSitZE
IOS7.x下UIGraphicsGetImageFromCurrentImageContext引發(fā)內(nèi)存暴漲所坯,導(dǎo)致應(yīng)用被結(jié)束掉
http://blog.163.com/l1_jun/blog/static/1438638820155593641529/
在iOS中與CGContextRef的內(nèi)存泄漏
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緩存圖片的機(jī)制(轉(zhuǎn))
http://blog.csdn.net/zhun36/article/details/8900327
近來(lái)一個(gè)swift項(xiàng)目用uicollectionview 用sdwebimage 加載圖片谆扎,發(fā)生內(nèi)存猛增,直接閃退的情況芹助,簡(jiǎn)單說(shuō)一下解決方案
http://www.myexception.cn/swift/2033029.html
關(guān)于SDWebImage加載高清圖片導(dǎo)致app崩潰的問(wèn)題
http://www.bubuko.com/infodetail-956863.html
SDWebImage加載大圖導(dǎo)致的內(nèi)存警告問(wèn)題
http://blog.csdn.net/richer1997/article/details/43481959
解決MWPhotoBrowser中的SDWebImage加載大圖導(dǎo)致的內(nèi)存警告問(wèn)題
http://my.oschina.net/u/1244672/blog/510379
使用SDWebImage加載大量圖片后造成內(nèi)存泄露的解決辦法