圖片的加載流程
參考下圖大致的加載流程
圖片初始創(chuàng)建是不會解壓的伶跷,只有在顯示前才會去準備解壓筋蓖,這樣如果有很多圖片同時需要展示就會造成主線任務(wù)繁重侣诺。
另外在我們展示圖片是有時為了降低頻繁操作工作量,會選擇異步解碼圖片弛姜。主體思路為:在子線程糜芳,將原始的圖片渲染成新的以字節(jié)顯示的圖片飒货。
代碼操作如下(SDWebImage
解決方案),其本質(zhì)是:
在子線程直接通過新創(chuàng)建一張位圖的方式繪制一張圖片峭竣。此時就已經(jīng)完成了解壓操作塘辅,可以后續(xù)直接使用。
// 以下代碼需要自己操作異步方案處理
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
if (image.images) {
// Do not decode animated images
return image;
}
CGImageRef imageRef = image.CGImage;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
int infoMask = (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);
UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(decompressedImageRef);
return decompressedImage;
}
繪制一個純色的圖片
-
使用
UIGraphicsImageRenderer
進行繪制皆撩,內(nèi)部會走CoreImage
這一套推薦使用這種方式扣墩,擁有緩存機制使用更加方便,不用管理上下文毅访。
iOS10
后可用
UIGraphicsImageRenderer *render = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(100, 100)]; // 指定渲染區(qū)域大小
UIImage *img = [render imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
[UIColor.blueColor setFill];
[rendererContext fillRect:CGRectMake(0, 0, 100, 100)];
}];
- 使用
Quartz2D
繪制
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, UIScreen.mainScreen.scale);
[UIColor.redColor setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(),CGRectMake(0, 0, 100, 100));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(10, 3, 100, 100)];
imgV.image = image;
[self.view addSubview:imgV];
壓縮大圖至指定尺寸
- 使用以下方案沮榜,會有內(nèi)存峰值出現(xiàn)
- (void)testDrawWithRender {
UIImage *image = [UIImage imageNamed:@"zz"];
UIGraphicsImageRenderer *render = [[UIGraphicsImageRenderer alloc] initWithSize:self.renderSize];
UIImage *img = [render imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
[image drawInRect:CGRectMake(0, 0, self.renderSize.width, self.renderSize.height)];
}];
UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(10, 3, self.renderSize.width, self.renderSize.height)];
imgV.image = img;
[self.view addSubview:imgV];
}
-
使用
ImageIO
渲染出一張縮略圖ImageIO
能夠在不產(chǎn)生dirty memory
的情況下讀取到圖片尺寸和元數(shù)據(jù)信息,其內(nèi)存損耗等于縮減后的圖片尺寸產(chǎn)生的內(nèi)存占用喻粹。
- (void)testDrawWithImageIO {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"zz" ofType:@"png"]];
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
// UIImage *image = [UIImage imageNamed:@"zz.png"]; // 如果使用了這種方式蟆融,那么大圖一定會加載到內(nèi)存中,會有峰值出現(xiàn)
// NSData *data = UIImagePNGRepresentation(image);
// CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef)@{
(id)kCGImageSourceCreateThumbnailFromImageIfAbsent:@(YES),
(id)kCGImageSourceThumbnailMaxPixelSize:@100, // 最大像素守呜,如果設(shè)置很小就不清楚
(id)kCGImageSourceShouldCache:@YES
};
// 生成縮略圖
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(imageSourceRef, 0, options);
UIImage *img = [UIImage imageWithCGImage:imageRef];
// 釋放內(nèi)存
CGImageRelease(imageRef);
CFRelease(imageSourceRef);
UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(10, 3, self.renderSize.width, self.renderSize.height)];
imgV.image = img;
[self.view addSubview:imgV];
}
常用枚舉屬性解析析:
-
kCGImageSourceThumbnailMaxPixelSize
設(shè)置縮略圖最大像素值型酥,圖片尺寸不會發(fā)生改變
-
kCGImageSourceCreateThumbnailFromImageIfAbsent
如果圖像源文件中不存在縮略圖,是否應(yīng)自動為圖像創(chuàng)建縮略圖查乒。
縮略圖是根據(jù)完整圖像創(chuàng)建的弥喉,受
kCGImageSourceThumbnailMaxPixelSize
指定的限制。如果未指定最大像素大小玛迄,則縮略圖是完整圖像的大小由境,這在大多數(shù)情況下是不可取的。此鍵必須是
CFBoolean
值。默認值為kCFBooleanFalse
虏杰。可以在傳遞給函數(shù)
CGImageSourceCreateThumbnailAtIndex
的選項字典中提供此鍵讥蟆。 -
kCGImageSourceCreateThumbnailFromImageAlways
是否應(yīng)根據(jù)完整圖像創(chuàng)建縮略圖,即使圖像源文件中存在縮略圖纺阔。
-
kCGImageSourceCreateThumbnailWithTransform
縮略圖是否應(yīng)根據(jù)全圖像的方向和像素縱橫比進行旋轉(zhuǎn)和縮放瘸彤。此鍵的值必須是
CFBoolean
值。默認值為kCFBooleanFalse
笛钝。 -
kCGImageSourceShouldCache
圖像是否應(yīng)以解碼形式緩存
總結(jié)
通過了解圖片加載機制质况,可以對后續(xù)優(yōu)化方向和理解一些框架設(shè)計思路有很大的幫助。
本文記錄些代碼操作都是工作中常用的方案(復制可用)玻靡,在此也作為一個留檔结榄。
參考:
https://segmentfault.com/a/1190000002776279
https://blog.jamchenjun.com/2018/08/22/image-and-graphics-best-practices.html