相冊浏覽器/選擇器/照相機Demo:LGPhotoBrowser
大多數項目中都會用到相冊浏覽和選擇功能,如果需要使用到自定義相冊浏覽器,那麼,性能優化將是一個很重要的課題。畢竟操作對象是圖片這樣相對較大寫數據單位。今天就針自定義相冊浏覽選擇器四個優化點進行剖析:
縮略圖頁面加載速度優化
縮略圖頁面滑動流暢度優化
大圖浏覽滑動流暢度優化
內存優化
先看看自定義相冊的兩個主要界面:


1.縮略圖頁面加載速度優化
如果本地相冊有200張以上的照片,那麼縮略圖頁面的加載速度就顯得尤為重要。
首先,要保證縮略圖界面的控制器在沒有加載照片的時候,從viewDidLoad到viewDidAppear的時間不能太長。之前用的MWPhotoBrowser,從viewDidLoad開始到viewDidAppear完成的耗時是700ms,這個時間就太長。
其次,是從本地讀取asset的過程不能太長。獲取一個group的所有asset方法如下:
- (void) getGroupPhotosWithGroup : (ZLPhotoPickerGroup *) pickerGroup finished : (groupCallBackBlock ) callBack{
NSMutableArray *assets = [NSMutableArray array];
ALAssetsGroupEnumerationResultsBlock result = ^(ALAsset *asset , NSUInteger index , BOOLBOOL *stop){
if (asset) {
[assets addObject:asset];
}else{
callBack(assets);
}
};
[pickerGroup.group enumerateAssetsUsingBlock:result];
}每取一張照片,就要執行一次代碼塊result。如果本地相冊中有1000張照片,那麼這個代碼塊就要執行1000次,所有這個代碼塊絕對不能臃腫,盡量簡潔!!!
2. 縮略圖頁面滑動流暢度優化
1)cell重用
縮略圖頁面的使用collectionView展示,每一個cell上實際有兩個子視圖,一個是裡面有對勾的小圓圈,另一個是透明的按鈕。所以在cell重用的時候就要注意了,這兩個子視圖也要加入到重用的行列中,千萬不要每加載一個cell就刪除所有子視圖然後再重新安裝。試驗證明,重用機制發揮好了,可以很大程度改善流暢度。
代碼如下:
/**
* 每個cell右上角的選擇按鈕
*/
- (void)setupTickButtonOnCell:(ZLPhotoPickerCollectionViewCell *)cell
AtIndex:(NSIndexPath *)indexPath
{
UIButton *tickButton = nil;
if (cell.contentView.subviews.count == 2 && [cell.contentView.subviews[1] isKindOfClass:[UIButton class]]) {//如果是重用cell,則不用再添加button
tickButton = cell.contentView.subviews[1];
} else {
tickButton = [[UIButton alloc] init];
tickButton.frame = CGRectMake(cell.frame.size.width - 40, 0, 40, 40);
[tickButton setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:tickButton];
[tickButton addTarget:self action:@selector(tickBtnTouched:) forControlEvents:UIControlEventTouchUpInside];
}
//runtime 關聯對象
objc_setAssociatedObject(tickButton, @"tickBtn", indexPath, OBJC_ASSOCIATION_ASSIGN);
}2)獲取縮略圖
在ALAsset類中有兩種獲取縮略圖的方法:thumbnail和aspectRatioThumbnail,第一個是普通縮略圖,第二個是按比例的縮略圖,實驗證明前者比後者快。實現方法如下:
慢
- (UIImage*)thumbImage{
return [UIImage imageWithCGImage:[self.assetaspectRatioThumbnail]];
}快
- (UIImage*)thumbImage{
return [UIImage imageWithCGImage:[self.assetthumbnail]];
}thumbnail方法要比aspectRatioThumbnail快很多。
但是,在ios9上,用thumbnail方法取得的縮略圖顯示出來不清晰。
所以,在代碼中可以加判斷,在iOS9上使用前者,其他使用後者。這樣可以在一定程度上提高流暢度。
3.大圖浏覽滑動流暢度優化
大圖浏覽界面使用collectionView展示,一張圖占據一個cell。一般情況下,collectionView需要加載cell時,就用數據源方法中取一個cell。在數據源方法中需要給cell獲取ALAsset對象的fullResolutionImage,獲取方法如下:
- (UIImage*)originImage{
UIImage *image = [UIImage imageWithCGImage:[[self.assetdefaultRepresentation]fullResolutionImage]];
return image;
}那麼問題來了,執行這個方法需要花費大概80ms,這就會產生卡頓感。
那怎麼辦?用異步加載?如果用異步的話,在originImage方法執行完之前,需要加載的cell已經暴露在視野中,是背景色,等fullResolutionImage執行完了,圖片會突然蹦出。這樣的體驗效果當然不行啦。
我的解決思路是:提前獲取image。概括的來說呢,就是在恰當的時機,提前在後台取到將要顯示的fullResolutionImage。
在viewDidLoad中獲取當前圖片的前一張image和後一個image,這時程序中就有三張准備好的image,如果向左邊滑動,直接拿取右邊的圖片給cell。
這一步是重點。滑動過程中,異步加載下一張image,這個下一張是需要判斷滑動方向的。從本次滑動動作結束到下次滑動開始,中間這個時間段異步加載一張圖片,時間是充裕的。那麼,在什麼時候開始異步加載呢?你可以在scrollView的這幾個代理方法中實現:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; // called on finger up as we are moving - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when // called on finger up if the user dragged. velocity is in points/millisecond. targetContentOffset may be changed to adjust where the scroll view comes to rest - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset NS_AVAILABLE_IOS(5_0);
我選擇最後一個,它有targetContentOffset,根據這個可以判斷手離開屏幕後,collectionView是否滑到下一張。因為用戶有時候會在滑動的時候猶豫一下,結果沒滑到下一張,這個時候在代碼裡就不要做多余的操作了。功能代碼如下:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
//如果滑動松開手後回滾動到下一個頁面
if (targetContentOffset->x != _beginDraggingContentOffsetX) {
DraggingDirect direct = [self getDraggingDirect];
//獲得currentPage
if (direct == LEFT) {
self.currentPage = (NSInteger)(scrollView.contentOffset.x / (scrollView.frame.size.width) + 0.9);
if (self.currentPage > self.photos.count - 1) {
self.currentPage --;
}
} else if (direct == RIGHT) {
self.currentPage = (NSInteger)(scrollView.contentOffset.x / (scrollView.frame.size.width));
}
//獲得image
dispatch_queue_t queue = dispatch_queue_create("BeginDecelerating", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//獲取下一張image
[self loadNextImageWithDirect:direct];
});
}
}
- (DraggingDirect)getDraggingDirect
{
DraggingDirect direct;
if (self.beginDraggingContentOffsetX == self.collectionView.contentOffset.x
) {
direct = MIDDLE;
} else if (self.beginDraggingContentOffsetX > self.collectionView.contentOffset.x) {
direct = RIGHT;
} else {
direct = LEFT;
}
return direct;
}
/**
* 根據上一次的滑動方向,加載下一張圖
*/
- (void)loadNextImageWithDirect:(DraggingDirect)direct {
if (direct == LEFT && self.currentPage < self.photos.count - 1){
if (!((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).photoImage) {
LGPhotoAssets *asset = ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).asset;
((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage + 1]).photoImage = [asset originImage];
}
} else if(direct == RIGHT && self.currentPage > 0){
if (!((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).photoImage) {
LGPhotoAssets *asset = ((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).asset;
((LGPhotoPickerBrowserPhoto *)self.photos[self.currentPage - 1]).photoImage = [asset originImage];
}
} else ;
}
4. 內存優化
如果選中9張原圖,點擊發送後內存瞬間暴增到100M以上。原因是點擊發送之後,程序會連續循環執行9次
UIImage*image = [UIImage imageWithCGImage:[[self.assetdefaultRepresentation]fullResolutionImage]];
這句代碼是解壓縮原分辨率照片的。
這句代碼是很耗內存的,但是沒辦法,這是系統的API。
解決辦法1:讓這九次解壓過程分開執行,一張圖片發送完成之後再解壓下一張圖片。這樣可以改善內存問題。
解決辦法2:利用NSData,如果項目中只需要得到原圖的原始數據,那麼就沒必要再把CGImage解壓成UIImage,避開解壓代碼。附上獲取NSData代碼:
- (NSData *)imageData:(ALAsset*)asset{
ALAssetRepresentation *assetRep = [asset defaultRepresentation];
NSUInteger size = [assetRep size];
uint8_t *buff = malloc(size);
NSError *err = nil;
NSUInteger gotByteCount = [assetRep getBytes:buff fromOffset:0 length:size error:&err];
if (gotByteCount) {
if (err) {
free(buff);
return nil;
}
}
return [NSData dataWithBytesNoCopy:buff length:size freeWhenDone:YES];
}以上就是本文的所有內容,難免會出纰漏,望各位匡正!
優化後的自定義相冊選擇浏覽器Demo:LGPhotoBrowser
關於相冊方面的基礎知識請參考: iOS的AssetsLibrary框架解讀與應用