最近在作一個UITableView的例子,發現滾動時的性能還不錯。但來回滾動時,第一次顯示的圖像不如再次顯示的圖像流暢,出現前會有稍許的停頓感。
因而我猜測顯示過的圖像確定是被緩存起來了,查了下文檔後發現果真如此。
後來在《Improving Image Drawing Performance on iOS》一文中找到了一些提示:原來在顯示圖像時,解壓和重採樣會消耗不少CPU時間;而若是預先在一個bitmap context裏畫出圖像,再緩存這個圖像,就能省去這些繁重的工做了。
接着我就寫了個例子程序來驗證:html
1 // ImageView.h 2 3 #import <UIKit/UIKit.h> 4 5 6 @interface ImageView : UIView { 7 UIImage *image; 8 } 9 10 @property (retain, nonatomic) UIImage *image; 11 12 @end
1 // ImageView.m 2 3 #include <mach/mach_time.h> 4 #import "ImageView.h" 5 6 7 @implementation ImageView 8 9 #define LABEL_TAG 1 10 11 static const CGRect imageRect = {{0, 0}, {100, 100}}; 12 static const CGPoint imagePoint = {0, 0}; 13 14 @synthesize image; 15 16 - (void)awakeFromNib { 17 if (!self.image) { 18 self.image = [UIImage imageNamed:@"random.jpg"]; 19 } 20 } 21 22 - (void)drawRect:(CGRect)rect { 23 if (CGRectEqualToRect(rect, imageRect)) { 24 uint64_t start = mach_absolute_time(); 25 [image drawAtPoint:imagePoint]; 26 uint64_t drawTime = mach_absolute_time() - start; 27 28 NSString *text = [[NSString alloc] initWithFormat:@"%lld", drawTime]; 29 UILabel *label = (UILabel *)[self viewWithTag:LABEL_TAG]; 30 label.text = text; 31 [text release]; 32 } 33 } 34 35 - (void)dealloc { 36 [super dealloc]; 37 [image release]; 38 } 39 40 @end
控制器的代碼我就不列出了,就是點按鈕時,更新view(調用[self.view setNeedsDisplayInRect:imageRect]),畫出一張圖,並在label中顯示消耗的時間。
值得一提的是,在模擬器上能夠直接用clock()函數得到微秒級的精度,但iOS設備上精度爲10毫秒。因而我找到了mach_absolute_time(),它在Mac和iOS設備上都有納秒級的精度。
測試用的是一張200x200像素的JPEG圖像,命名時加了@2x,在iPhone 4上第一次顯示時花了約300微秒,再次顯示約65微秒。
接下來就是見證奇蹟的時刻了,把這段代碼加入程序:ios
1 static const CGSize imageSize = {100, 100}; 2 3 - (void)awakeFromNib { 4 if (!self.image) { 5 self.image = [UIImage imageNamed:@"random.jpg"]; 6 if (NULL != UIGraphicsBeginImageContextWithOptions) 7 UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); 8 else 9 UIGraphicsBeginImageContext(imageSize); 10 [image drawInRect:imageRect]; 11 self.image = UIGraphicsGetImageFromCurrentImageContext(); 12 UIGraphicsEndImageContext(); 13 } 14 }
這裏須要判斷一下UIGraphicsBeginImageContextWithOptions是否爲NULL,由於它是iOS 4.0才加入的。
因爲JPEG圖像是不透明的,因此第二個參數就設爲YES。
第三個參數是縮放比例,iPhone 4是2.0,其餘是1.0。雖然這裏能夠用[UIScreen mainScreen].scale來獲取,但實際上設爲0後,系統就會自動設置正確的比例了。
值得一提的是,圖像自己也有縮放比例,普通的圖像是1.0(除了UIImage imageNamed:外,大部分API都只能得到這種圖像,並且縮放比例是不可更改的),高清圖像是2.0。圖像的點和屏幕的像素就是依靠2者的縮放比例來計算的,例如普通圖像在視網膜顯示屏上是1:4,而高清圖像在視網膜顯示屏上則是1:1。
接下來的drawInRect:把圖像畫到了當前的image context裏,這時就完成了解壓縮和重採樣的工做了。而後再從image context裏獲取新的image,這個image的縮放比例也能正確地和設備匹配。
再點下按鈕,發現時間已經縮短到12微秒左右了,以後的畫圖穩定在15微秒左右。
還能更快嗎?讓咱們來試試Core Graphics。
先定義一個全局的CGImageRef變量:緩存
1 static CGImageRef imageRef;
再在awakeFromNib中設置一下它的值:app
1 imageRef = self.image.CGImage;
最後在drawRect:中繪製:框架
1 CGContextRef context = UIGraphicsGetCurrentContext(); 2 CGContextDrawImage(context, imageRect, imageRef);
搞定運行一下,發現時間增長到33微秒左右了,並且圖像還上下顛倒了⋯
這個緣由是UIKit和Core Graphics的座標系y軸是相反的,因而加上2行代碼來修正:dom
1 CGContextRef context = UIGraphicsGetCurrentContext(); 2 CGContextTranslateCTM(context, 0, 100); 3 CGContextScaleCTM(context, 1, -1); 4 CGContextDrawImage(context, imageRect, imageRef);
這下圖像終於正常顯示了,時間縮短到了14微秒左右,成效不大,看來直接用-drawAtPoint:和-drawInRect:也足夠好了。
固然,這個例子正確的作法是用viewDidLoad或loadView,不過我懶得列出控制器代碼,因此就放awakeFromNib裏了。
2011年9月22日更新勘誤:
剛看到Mach Absolute Time Units這篇Q&A,發現mach_absolute_time()的單位是Mach absolute time unit,而不是納秒。它們之間的換算關係和CPU相關,不是一個常量。
最簡單的辦法是用CoreServices框架的AbsoluteToNanoseconds和AbsoluteToDuration函數來轉換。此外也能夠用mach_timebase_info函數來獲取這個比值。
我在iPhone 4上測得的numer和denom分別爲125和3,比值約爲42,所以本文所述的時間都須要乘以42。函數