W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
????繪圖實(shí)際消耗的時(shí)間通常并不是影響性能的因素。圖片消耗很大一部分內(nèi)存,而且不太可能把需要顯示的圖片都保留在內(nèi)存中,所以需要在應(yīng)用運(yùn)行的時(shí)候周期性地加載和卸載圖片。
????圖片文件加載的速度被CPU和IO(輸入/輸出)同時(shí)影響。iOS設(shè)備中的閃存已經(jīng)比傳統(tǒng)硬盤快很多了,但仍然比RAM慢將近200倍左右,這就需要很小心地管理加載,來(lái)避免延遲。
????只要有可能,試著在程序生命周期不易察覺(jué)的時(shí)候來(lái)加載圖片,例如啟動(dòng),或者在屏幕切換的過(guò)程中。按下按鈕和按鈕響應(yīng)事件之間最大的延遲大概是200ms,這比動(dòng)畫每一幀切換的16ms小得多。你可以在程序首次啟動(dòng)的時(shí)候加載圖片,但是如果20秒內(nèi)無(wú)法啟動(dòng)程序的話,iOS檢測(cè)計(jì)時(shí)器就會(huì)終止你的應(yīng)用(而且如果啟動(dòng)大于2,3秒的話用戶就會(huì)抱怨了)。
????有些時(shí)候,提前加載所有的東西并不明智。比如說(shuō)包含上千張圖片的圖片傳送帶:用戶希望能夠能夠平滑快速翻動(dòng)圖片,所以就不可能提前預(yù)加載所有圖片;那樣會(huì)消耗太多的時(shí)間和內(nèi)存。
????有時(shí)候圖片也需要從遠(yuǎn)程網(wǎng)絡(luò)連接中下載,這將會(huì)比從磁盤加載要消耗更多的時(shí)間,甚至可能由于連接問(wèn)題而加載失?。ㄔ趲酌腌妵L試之后)。你不能夠在主線程中加載網(wǎng)絡(luò)造成等待,所以需要后臺(tái)線程。
????在第12章“性能調(diào)優(yōu)”我們的聯(lián)系人列表例子中,圖片都非常小,所以可以在主線程同步加載。但是對(duì)于大圖來(lái)說(shuō),這樣做就不太合適了,因?yàn)榧虞d會(huì)消耗很長(zhǎng)時(shí)間,造成滑動(dòng)的不流暢?;瑒?dòng)動(dòng)畫會(huì)在主線程的run loop中更新,所以會(huì)有更多運(yùn)行在渲染服務(wù)進(jìn)程中CPU相關(guān)的性能問(wèn)題。
????清單14.1顯示了一個(gè)通過(guò)UICollectionView
實(shí)現(xiàn)的基礎(chǔ)的圖片傳送器。圖片在主線程中-collectionView:cellForItemAtIndexPath:
方法中同步加載(見(jiàn)圖14.1)。
清單14.1 使用UICollectionView
實(shí)現(xiàn)的圖片傳送器
#import "ViewController.h"
@interface ViewController()
@property (nonatomic, copy) NSArray *imagePaths;
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad
{
//set up data
self.imagePaths =
[[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"Vacation Photos"];
//register cell class
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imagePaths count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
//add image view
const NSInteger imageTag = 99;
UIImageView *imageView = (UIImageView *)[cell viewWithTag:imageTag];
if (!imageView) {
imageView = [[UIImageView alloc] initWithFrame: cell.contentView.bounds];
imageView.tag = imageTag;
[cell.contentView addSubview:imageView];
}
//set image
NSString *imagePath = self.imagePaths[indexPath.row];
imageView.image = [UIImage imageWithContentsOfFile:imagePath];
return cell;
}
@end
圖14.2 時(shí)間分析工具展示了CPU瓶頸
????這里提升性能唯一的方式就是在另一個(gè)線程中加載圖片。這并不能夠降低實(shí)際的加載時(shí)間(可能情況會(huì)更糟,因?yàn)橄到y(tǒng)可能要消耗CPU時(shí)間來(lái)處理加載的圖片數(shù)據(jù)),但是主線程能夠有時(shí)間做一些別的事情,比如響應(yīng)用戶輸入,以及滑動(dòng)動(dòng)畫。
????為了在后臺(tái)線程加載圖片,我們可以使用GCD或者NSOperationQueue
創(chuàng)建自定義線程,或者使用CATiledLayer
。為了從遠(yuǎn)程網(wǎng)絡(luò)加載圖片,我們可以使用異步的NSURLConnection
,但是對(duì)本地存儲(chǔ)的圖片,并不十分有效。
NSOperationQueue
????GCD(Grand Central Dispatch)和NSOperationQueue
很類似,都給我們提供了隊(duì)列閉包塊來(lái)在線程中按一定順序來(lái)執(zhí)行。NSOperationQueue
有一個(gè)Objecive-C接口(而不是使用GCD的全局C函數(shù)),同樣在操作優(yōu)先級(jí)和依賴關(guān)系上提供了很好的粒度控制,但是需要更多地設(shè)置代碼。
????清單14.2顯示了在低優(yōu)先級(jí)的后臺(tái)隊(duì)列而不是主線程使用GCD加載圖片的-collectionView:cellForItemAtIndexPath:
方法,然后當(dāng)需要加載圖片到視圖的時(shí)候切換到主線程,因?yàn)樵诤笈_(tái)線程訪問(wèn)視圖會(huì)有安全隱患。
????由于視圖在UICollectionView
會(huì)被循環(huán)利用,我們加載圖片的時(shí)候不能確定是否被不同的索引重新復(fù)用。為了避免圖片加載到錯(cuò)誤的視圖中,我們?cè)诩虞d前把單元格打上索引的標(biāo)簽,然后在設(shè)置圖片的時(shí)候檢測(cè)標(biāo)簽是否發(fā)生了改變。
清單14.2 使用GCD加載傳送圖片
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell"
forIndexPath:indexPath];
//add image view
const NSInteger imageTag = 99;
UIImageView *imageView = (UIImageView *)[cell viewWithTag:imageTag];
if (!imageView) {
imageView = [[UIImageView alloc] initWithFrame: cell.contentView.bounds];
imageView.tag = imageTag;
[cell.contentView addSubview:imageView];
}
//tag cell with index and clear current image
cell.tag = indexPath.row;
imageView.image = nil;
//switch to background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//load image
NSInteger index = indexPath.row;
NSString *imagePath = self.imagePaths[index];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
//set image on main thread, but only if index still matches up
dispatch_async(dispatch_get_main_queue(), ^{
if (index == cell.tag) {
imageView.image = image; }
});
});
return cell;
}
????當(dāng)運(yùn)行更新后的版本,性能比之前不用線程的版本好多了,但仍然并不完美(圖14.3)。
????我們可以看到+imageWithContentsOfFile:
方法并不在CPU時(shí)間軌跡的最頂部,所以我們的確修復(fù)了延遲加載的問(wèn)題。問(wèn)題在于我們假設(shè)傳送器的性能瓶頸在于圖片文件的加載,但實(shí)際上并不是這樣。加載圖片數(shù)據(jù)到內(nèi)存中只是問(wèn)題的第一部分。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: