常用的第三方庫:
關(guān)于圖片的下載第三方庫赋除,目前有三個選擇:
-
SDWebImage
是使用最廣泛的荆针,目前的項目中在用航背,而且還是直接源碼方式,連CocoaPod
都沒用 -
YYImage
據(jù)說可以替代SDWebImage
今魔,曾經(jīng)看過YYModel
和YYCache
涡贱,真的有意愿選擇 -
AFNetworking
,這個主要用來做網(wǎng)絡(luò)庫激挪,圖片的下載很少用垄分。SDWebImage
作者也說AFNetworking
值得用,采用系統(tǒng)的NSURLCache
實現(xiàn)緩存豺瘤。 -
SDWebImage
和YYImage
都用了自己的緩存蚕泽,并且會把圖片解碼操作提前做了须妻,可以加快UITableView
的顯示,但缺點是內(nèi)存占用會偏高司倚。緩存和UIImage
編解碼是這兩個庫的特色皿伺。 -
SDWebImage
項目中在用鸵鸥,先看他的實現(xiàn)方式妒穴,其他的等以后有機(jī)會再學(xué)
讀取內(nèi)存緩存
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
-
UIImage *image
很好理解杰赛,應(yīng)該就是直接給UIImageView
用的乏屯。根據(jù)后面代碼閱讀辰晕,這里的不是普通的UIImage
,應(yīng)該是考慮了乘數(shù)因子scale
和解碼之后的UIImage
扎唾,是可以直接在屏幕上顯示的胸遇。 - 這里的
NSData *diskData
是特指gif
動圖的數(shù)據(jù)。gif
動圖是不考慮解碼的逗威。
@property (strong, nonatomic, nonnull) NSCache *memCache;
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
軟件緩存直接使用系統(tǒng)的NSCache
實現(xiàn),比較簡單
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) {
return data;
}
return nil;
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
如果是Gif
動圖,不能直接用Image
嫉柴,會去磁盤上讀NSData
,這些數(shù)據(jù)存在磁盤某個目錄下登馒,文件名是key
的md5
值,而這個key
是圖片的url
如果是普通圖片济欢,內(nèi)存中Image
可以直接用,默認(rèn)的話是解碼過的半等,可以直接在屏幕上顯示
讀取磁盤緩存
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
-
self.ioQueue
是一個串行隊列杀饵,放在一個@autoreleasepool
中估計是為了省內(nèi)存 - 這里的
diskData
切距,從磁盤的某處(url
的MD5
值為文件名,帶擴(kuò)展名)葡幸,讀取NSData
格式的數(shù)據(jù)床蜘。 -
self.config.shouldCacheImagesInMemory
默認(rèn)值是YES
肤粱,就是說從磁盤上讀出來數(shù)據(jù)同時也會在內(nèi)存中保存一份。下次就直接在內(nèi)存緩存中命中了蛮穿,不需要再到緩慢的磁盤緩存中讀取庶骄。 - 大小
cost
是像素,要考慮乘數(shù)因子scale
NSUInteger SDCacheCostForImage(UIImage *image) {
return image.size.height * image.size.width * image.scale * image.scale;
}```
* 這里的`diskImage`和普通的`UIImage`不同践磅,考慮了乘數(shù)因子`scale`和解碼之后单刁,是可以直接在屏幕上顯示的。
#### 讀取過程
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}
* 從磁盤讀取`NSData`格式的數(shù)據(jù)府适,和前面用的都是同一個函數(shù)
* 將`NSData`格式的數(shù)據(jù)轉(zhuǎn)化為`UIImage`
* `UIImage`考慮乘數(shù)因子`scale`
* `self.config.shouldDecompressImages`默認(rèn)是`YES`羔飞,`UIImage`需要經(jīng)過解碼卡儒,成為能直接在屏幕上顯示的`UIImage`缀磕。
不經(jīng)過解碼的普通`UIImage`,會在主線程進(jìn)行解碼后再顯示在屏幕上滞项,造成`CPU`占用率過高。平時關(guān)系不大仁热,在`UITableView`快速滑動,并且圖片數(shù)據(jù)量較大的時候,會有卡頓現(xiàn)象發(fā)生。
解碼之后的`UIImage`措伐,可以直接在屏幕上顯示,但是數(shù)據(jù)量很大泳唠,所以高清圖在低端機(jī)上內(nèi)存暴漲镶奉,還發(fā)燙亿蒸,就是這個原因。下面的有篇文章說的就是這個,解決方案就是把`self.config.shouldDecompressImages`設(shè)置為`NO`邻眷,這樣就解決內(nèi)存占用過大的問題了阿宅。
另外往湿,對于`JPEG`圖蟀俊,`iPhone4s`以后的機(jī)子都有硬件編解碼的,所以為了減少內(nèi)存占用,也可以考慮把這個開關(guān)關(guān)閉。
**是省CPU時間乍炉,還是省內(nèi)存困檩?**默認(rèn)選擇了省cpu時間喉誊,減少卡頓現(xiàn)象邀摆,提升體驗
#### `NSData`轉(zhuǎn)換為`UIImage`
-
(nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
if (!data) {
return nil;
}UIImage *image;
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data];
if (imageFormat == SDImageFormatGIF) {
image = [UIImage sd_animatedGIFWithData:data];
} else if (imageFormat == SDImageFormatWebP) {
image = [UIImage sd_imageWithWebPData:data];
} else {
image = [[UIImage alloc] initWithData:data];
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
}
}
return image;
}
* 這是`UIImage`的一個類別`category`
* `gif`動圖和`webp`格式有特殊的生成方式
* 這里還考慮了圖片的方向,如果不是朝上伍茄,圖片生成方式還不一樣
#### 判斷圖片的格式
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP
};
-
(SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return SDImageFormatJPEG;
case 0x89:
return SDImageFormatPNG;
case 0x47:
return SDImageFormatGIF;
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52:
// R as RIFF for WEBP
if (data.length < 12) {
return SDImageFormatUndefined;
}NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return SDImageFormatWebP; }
}
return SDImageFormatUndefined;
}
* 這是`NSData`的一個類別
* 默認(rèn)是`JPEG`格式
* 第一個字節(jié)代表了圖片的格式
#### 普通的`UIImage`轉(zhuǎn)換為像素`UIImage`
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) {
return nil;
}
if ((image.images).count > 0) {
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
} else {
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
CGFloat scale = 1;
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
return image;
}
}
* `gif`動圖和普通圖片的區(qū)別`(image.images).count > 0`
* 乘數(shù)因子`scale`默認(rèn)為1栋盹,根據(jù)圖片名稱中的`@2x.` 以及` @3x.`來判斷是2倍圖還是3倍圖
* `gif`動圖有包含的`images`和持續(xù)時間`duration`兩個重要特征
#### 圖片解碼
-
(BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}// do not decode animated images
if (image.images != nil) {
return NO;
}CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
// do not decode images with alpha
if (anyAlpha) {
return NO;
}return YES;
}
* `gif`動圖不用解碼`image.images != nil`和`(image.images).count > 0`是一個意思
* 有透明度信息的`Alpha`的圖片不用解碼
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
-
(nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool{CGImageRef imageRef = image.CGImage; CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef]; size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); size_t bytesPerRow = kBytesPerPixel * width; // kCGImageAlphaNone is not supported in CGBitmapContextCreate. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast // to create bitmap graphics contexts without alpha info. CGContextRef context = CGBitmapContextCreate(NULL, width, height, kBitsPerComponent, bytesPerRow, colorspaceRef, kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); if (context == NULL) { return image; } // Draw the image into the context and retrieve the new bitmap image without alpha CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context); UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation]; CGContextRelease(context); CGImageRelease(imageRefWithoutAlpha); return imageWithoutAlpha;
}
}
* 這個過程是很耗內(nèi)存的,所以用一個`@autoreleasepool`進(jìn)行內(nèi)存管理
* 把`pixel`化的`UIImage`轉(zhuǎn)化為`bitmap`的`context`
* 利用這個`context`畫一個`UIImage`
* 這個`UIImage`可以直接在屏幕上顯示了敷矫,不需要`CPU`或者硬件(`JPEG`)解碼了例获,加快了顯示,避免了`UITableView`快速滑動過程中的“卡頓”現(xiàn)象
## 下載完成后存緩存
-
(void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;if (!data && image) { SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data]; data = [image sd_imageDataAsFormat:imageFormatFromData]; } [self storeImageDataToDisk:data forKey:key]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } });
} else {
if (completionBlock) {
completionBlock();
}
}
}
* 先在內(nèi)存中保存一份
* 是否保存到磁盤由參數(shù)`toDisk` 控制曹仗,比如設(shè)置了`SDWebImageCacheMemoryOnly`的話榨汤,就不保存到磁盤了
* 保存到磁盤過程在一個串行隊列`self.ioQueue`中執(zhí)行,由`dispatch_async`開辟一個子線程來完成怎茫。
* `image`和`key`(`url`)參數(shù)是必須的收壕,不然不會保存。不管是`gif`動圖或者普通的`png遭居、jpeg`圖啼器,`image`參數(shù)都是有的
* `NSData *`格式的`imageData`參數(shù)是可以為空的,如果不為空俱萍,那么就是`gif`動圖的數(shù)據(jù)端壳,直接存磁盤了。
如果為空枪蘑,那么就把`image`參數(shù)轉(zhuǎn)換為`NSData *`之后存到磁盤
* 下面這段代碼寫的比較差损谦;也可能是個`bug`,不理解為什么會這些寫
if (!data && image) {
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}```
data
只有nil
的情況岳颇,才會進(jìn)入照捡,所以sd_imageFormatForImageData
的入?yún)⑹谴_定的nil
,沒有必要給data
话侧。imageFormatFromData
是確定的SDImageFormatUndefined
另外image
一進(jìn)入函數(shù)的時候就查過栗精,到這里肯定不是nil
,沒有必要再查一下
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
NSData *imageData = nil;
if (self) {
int alphaInfo = CGImageGetAlphaInfo(self.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL usePNG = hasAlpha;
// the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel
if (imageFormat != SDImageFormatUndefined) {
usePNG = (imageFormat == SDImageFormatPNG);
}
if (usePNG) {
imageData = UIImagePNGRepresentation(self);
} else {
imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0);
}
}
return imageData;
}
- 這是
UIImage
的一個類別category
; - 存入磁盤的
NSData *
格式由PNG
或者JPEG
轉(zhuǎn)換而來悲立; - 默認(rèn)是
JPEG
類型 - 如果是
.png
圖片鹿寨,或者有Alpha
信息,那么是PNG
格式 - 由于調(diào)用者傳入的參數(shù)都是
SDImageFormatUndefined
薪夕,所以就簡化為有Alpha
信息就是PNG
格式數(shù)據(jù)脚草,其他都是JPEG
格式數(shù)據(jù)。 - 磁盤上的數(shù)據(jù)是沒有解碼的圖片數(shù)據(jù)原献,體積比較辛罂;內(nèi)存中的是解碼過的圖片數(shù)據(jù)姑隅,體積比較大写隶。
下載后解碼
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}
if (error) {
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
* Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
* and images for which responseFromCached is YES (only the ones that cannot be cached).
* Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
// hack
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else if (self.imageData) {
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) {
if (self.shouldDecompressImages) {
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
image = [UIImage decodedAndScaledDownImageWithImage:image];
[self.imageData setData:UIImagePNGRepresentation(image)];
} else {
image = [UIImage decodedImageWithImage:image];
}
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
}
- 采用
NSURLSession
的dataTask
進(jìn)行下載,并沒有用downloadTask
- 下載的數(shù)據(jù)保存在self.imageData中粤策,類型是
NSData *
@property (strong, nonatomic, nullable) NSMutableData *imageData;
- 通過函數(shù)
sd_imageWithData
將NSData *
格式的數(shù)據(jù)轉(zhuǎn)換為普通的UIImage *
- 通過函數(shù)
scaledImageForKey
將普通的UIImage *
轉(zhuǎn)換為考慮了乘數(shù)因子scale
的像素UIImage *
- 通過函數(shù)
decodedImageWithImage
進(jìn)行解碼樟澜,生成能在屏幕上直接顯示的UIImage *
設(shè)置磁盤緩存最大值
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
- 默認(rèn)情況下
self.config.maxCacheSize = 0;
不會調(diào)整磁盤緩存的大小 - 默認(rèn)緩存的有效時間是1周,在一周之內(nèi)訪問過的圖片都緩存下來的叮盘。過期的文件會被刪除秩贰。
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
- 如果有必要,可以設(shè)置
self.config.maxCacheSize
為一個合理值柔吼,減少對手機(jī)磁盤的占用毒费。每當(dāng)超過這個設(shè)定值,將會刪除一些緩存文件愈魏,直到總?cè)萘啃∮谶@個設(shè)定值得一半觅玻。 - 這個控制的是磁盤緩存的文件,對于由于解碼而造成的內(nèi)存超標(biāo)培漏,這個參數(shù)不起作用溪厘。
清除內(nèi)存
當(dāng)出現(xiàn)內(nèi)存告警時,會清緩存(內(nèi)存緩存)牌柄。解碼導(dǎo)致內(nèi)存占用大畸悬,用空間換時間,使界面顯示更流暢珊佣,不“卡頓”這個是有的蹋宦。
不過由于內(nèi)存占用過大而導(dǎo)致崩潰,應(yīng)該不至于吧?
#pragma mark - Cache clean Ops
- (void)clearMemory {
[self.memCache removeAllObjects];
}
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
關(guān)于大圖的壓縮解碼
SDWebImageDownloaderScaleDownLargeImages
以及SDWebImageScaleDownLargeImages
默認(rèn)是不設(shè)置的咒锻。如果設(shè)置冷冗,就會調(diào)用decodedAndScaledDownImageWithImage
進(jìn)行壓縮解碼
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.0f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
if (![UIImage shouldScaleDownImage:image]) {
return [UIImage decodedImageWithImage:image];
}
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool {
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
// Determine the scale ratio to apply to the input image
// that results in an output image of the defined size.
// see kDestImageSizeMB, and how it relates to destTotalPixels.
float imageScale = kDestTotalPixels / sourceTotalPixels;
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
// current color space
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(destBitmapData,
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (destContext == NULL) {
free(destBitmapData);
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// Now define the size of the rectangle to be used for the
// incremental blits from the input image to the output image.
// we use a source tile width equal to the width of the source
// image due to the way that iOS retrieves image data from disk.
// iOS must decode an image from disk in full width 'bands', even
// if current graphics context is clipped to a subrect within that
// band. Therefore we fully utilize all of the pixel data that results
// from a decoding opertion by achnoring our tile size to the full
// width of the input image.
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
// The source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
}
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
-
shouldDecodeImage
判斷是否需要解碼,空的惑艇,有alpha
信息的蒿辙,GIF
動圖都不會解碼 -
shouldScaleDownImage
判斷是否需要壓縮;如果解壓后大小超過60M
,那么就需要壓縮须板,否則就不需要碰镜。
如果不需要壓縮,那么就調(diào)用decodedImageWithImage
進(jìn)行無壓縮的解碼 - 壓縮后的目標(biāo)是把大小壓縮到
60M
以內(nèi)
static const CGFloat kDestImageSizeMB = 60.0f;
比如原先100M的位圖(解碼后的格式)壓縮后得到的位圖在60M
以內(nèi) - 壓縮不是一次完成的习瑰,而是一塊一塊完成的,每次的大小是
20M
static const CGFloat kSourceImageTileSizeMB = 20.0f;
- 他的方法是寬度保持不變秽荤,然后高度
y
一塊一塊往下移動 - 往下移多少次呢甜奄?高度除一下儡陨,有余數(shù)加1
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
- 又想起了網(wǎng)上說的解碼導(dǎo)致內(nèi)存暴漲蒜哀,對于高清圖隘膘,關(guān)閉解碼彬碱。這個壓縮解碼瓦糕,不正是為了解決高清圖解碼后占用內(nèi)存太多的問題嗎喂柒?
所以除了網(wǎng)上說的關(guān)閉解碼功能辣卒,也可以嘗試一下打開這個壓縮解碼功能拧晕,也就是設(shè)置SDWebImageScaleDownLargeImages
參考文章
使用SDWebImage和YYImage下載高分辨率圖紊扬,導(dǎo)致內(nèi)存暴增的解決辦法