前言
為什么需要對(duì)圖片進(jìn)行解碼操作?
事實(shí)上,不管是 JPEG 還是 PNG 圖片,都是一種壓縮的位圖圖形格式。只不過(guò) PNG 圖片是無(wú)損壓縮,并且支持 alpha 通道,而 JPEG 圖片則是有損壓縮,可以指定 0-100% 的壓縮比,因此,在將磁盤(pán)中的圖片渲染到屏幕之前,必須先要得到圖片的原始像素?cái)?shù)據(jù),才能執(zhí)行后續(xù)的繪制操作,這就是為什么需要對(duì)圖片解壓縮的原因。詳見(jiàn) 談?wù)?iOS 中圖片的解壓縮 IOS 中圖片格式問(wèn)題與性能優(yōu)化 iOS開(kāi)發(fā):圖片格式與性能優(yōu)化
1.圖片解碼到底有多卡?
測(cè)試方法比較簡(jiǎn)單,在一個(gè)可以tableView里面展示圖片,圖片是已經(jīng)放在本地的10張圖片,每張圖片大于1MB
代碼如下:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BannerTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BannerTableViewCell" forIndexPath:indexPath];
// 獲取圖片
NSInteger index = 0;
index = indexPath.row%10;
NSString *imageName = [NSString stringWithFormat:@"backImage%ld",(long)index];
//UIImage *image = [UIImage imageNamed:imageName];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
cell.contentImageView.image = image;
return cell;
}
細(xì)心的同學(xué)可能已經(jīng)注意到了我在代碼中寫(xiě)了兩種方式加載圖片。
一種是: [UIImage imageNamed:imageName]
一種是: [UIImage imageWithContentsOfFile:path]
后面我再解釋為什么需要對(duì)比這兩種加載方式,先上加載的結(jié)果吧。
1>使用[UIImage imageWithContentsOfFile:path]

2>使用[UIImage imageNamed:imageName]

兩種方式都實(shí)際滑動(dòng)一分鐘, 可以清晰的看到,兩種加載方式一開(kāi)始都幀數(shù)很低,但是使用imageNamed: 的很快幀數(shù)就恢復(fù)到60幀,但是使用imageWithContentsOfFile:會(huì)一直卡頓,那是因?yàn)槭褂胕mageNamed: 會(huì)緩存圖片,但是imageWithContentsOfFile: 則不會(huì),而且 使用imageWithContentsOfFile: 出現(xiàn)了明顯的卡頓,出現(xiàn)了明顯的丟幀從曲線上來(lái)看能明顯看到兩種方式的差異問(wèn)題。
再來(lái)解釋我們使用的兩種加載方式,使用 imageWithContentsOfFile: 實(shí)際上是模擬網(wǎng)絡(luò)下載圖片到本地后,再?gòu)谋镜丶虞d展示圖片的過(guò)程,imageNamed:方式則是模擬從Assets.xcassets 里加載圖片的情況,可以明顯看到蘋(píng)果是對(duì)從Assets.xcassets 里加載圖片做過(guò)優(yōu)化的。
2.如何對(duì)圖片解碼部分進(jìn)行優(yōu)化
方案很簡(jiǎn)單: 解碼的過(guò)程是可以直接放在子線程中的,解碼完成后可以在主線程中將圖片賦值給imageView.image并且緩存下來(lái),下次再次查找到相同的圖片直接在緩存中讀取就可以了。
這個(gè)過(guò)程是不是聽(tīng)起來(lái)很熟悉,是的,這個(gè)過(guò)程已經(jīng)有很有多的第三方庫(kù)實(shí)現(xiàn)過(guò)了,其中最有名的就是SDWebImage了,SDWebImage的解碼方法是decodedImageWithImage,使用了CGContextDrawImage,有興趣的小伙伴們可以抽空去看看,在這我就不贅述了,直接上優(yōu)化代碼:
[self queryImageCache:imageName block:^(UIImage *image) {
cell.contentImageView.image = image;
}];
- (void)queryImageCache:(NSString *)filename block:(void(^)(UIImage *image))block
{
//從內(nèi)存去取,如果沒(méi)取到,就直接讀取文件,在緩存起來(lái)
UIImage *image = [self.memCache objectForKey:filename];
if(image)
{
dispatch_async(dispatch_get_main_queue(), ^{
if(block)
block(image);
});
}
else
{
//把解壓操作放到子線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:path];
image = [UIImage decodedImageWithImage:image];
[self.memCache setObject:image forKey:filename];
// 同步主線程
dispatch_async(dispatch_get_main_queue(), ^{
if(block)
block(image);
});
});
}
}
實(shí)驗(yàn)以上方法后再次進(jìn)行之前的方法查看FPS和CPU使用情況

| 名稱(chēng) | FPS (平均) | CPU(平均) | 實(shí)驗(yàn)時(shí)間 |
|---|---|---|---|
| imageWithContentsOfFile: | 47.8 | 28% | 1min |
| imageNamed: | 58.8 | 10% | 1min3 |
| 優(yōu)化后 | 59.9 | 7% | 1min9 |
可以明顯看到不論是幀數(shù)還是CPU使用情況,優(yōu)化后的列表情況都明顯優(yōu)異多了,雖然這個(gè)過(guò)程SDWebImage已經(jīng)實(shí)現(xiàn)了,但是放在我還是想放在這里來(lái)講解下,希望對(duì)各位有所幫助。
參考過(guò)以下大大的技術(shù)博客:
https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
http://www.itdecent.cn/p/f9ef5dba9ba3?_dt_push=1
http://www.cocoachina.com/cms/wap.php?action=article&id=24599