轉:點擊打開連接html
最近在作一個UITableView的例子,發現滾動時的性能還不錯。但來回滾動時,第一次顯示的圖像不如再次顯示的圖像流暢,出現前會有稍許的停頓感。因而我猜測顯示過的圖像確定是被緩存起來了,查了下文檔後發現果真如此。後來在《Improving Image Drawing Performance on iOS》一文中找到了一些提示:原來在顯示圖像時,解壓和重採樣會消耗不少CPU時間;而若是預先在一個bitmap context裏畫出圖像,再緩存這個圖像,就能省去這些繁重的工做了。接着我就寫了個例子程序來驗證:ios
// ImageView.h #import <UIKit/UIKit.h> @interface ImageView : UIView { UIImage *image; } @property (retain, nonatomic) UIImage *image; @end
// ImageView.m #include <mach/mach_time.h> #import "ImageView.h" @implementation ImageView #define LABEL_TAG 1 static const CGRect imageRect = {{0, 0}, {100, 100}}; static const CGPoint imagePoint = {0, 0}; @synthesize image; - (void)awakeFromNib { if (!self.image) { self.image = [UIImage imageNamed:@"random.jpg"]; } } - (void)drawRect:(CGRect)rect { if (CGRectEqualToRect(rect, imageRect)) { uint64_t start = mach_absolute_time(); [image drawAtPoint:imagePoint]; uint64_t drawTime = mach_absolute_time() - start; NSString *text = [[NSString alloc] initWithFormat:@"%lld", drawTime]; UILabel *label = (UILabel *)[self viewWithTag:LABEL_TAG]; label.text = text; [text release]; } } - (void)dealloc { [super dealloc]; [image release]; } @end
控制器的代碼我就不列出了,就是點按鈕時,更新view(調用[self.view setNeedsDisplayInRect:imageRect]),畫出一張圖,並在label中顯示消耗的時間。值得一提的是,在模擬器上能夠直接用clock()函數得到微秒級的精度,但iOS設備上精度爲10毫秒。因而我找到了mach_absolute_time(),它在Mac和iOS設備上都有納秒級的精度。測試用的是一張200x200像素的JPEG圖像,命名時加了@2x,在iPhone 4上第一次顯示時花了約300微秒,再次顯示約65微秒。接下來就是見證奇蹟的時刻了,把這段代碼加入程序:緩存
static const CGSize imageSize = {100, 100}; - (void)awakeFromNib { if (!self.image) { self.image = [UIImage imageNamed:@"random.jpg"]; if (NULL != UIGraphicsBeginImageContextWithOptions) UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); else UIGraphicsBeginImageContext(imageSize); [image drawInRect:imageRect]; self.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } }
這裏須要判斷一下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變量:app
static CGImageRef imageRef;
再在awakeFromNib中設置一下它的值:框架
imageRef = self.image.CGImage;
最後在drawRect:中繪製:dom
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextDrawImage(context, imageRect, imageRef);
搞定運行一下,發現時間增長到33微秒左右了,並且圖像還上下顛倒了⋯
這個緣由是UIKit和Core Graphics的座標系y軸是相反的,因而加上2行代碼來修正:函數
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, 0, 100); CGContextScaleCTM(context, 1, -1); 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。
測試