背景
前不久我做了一個富文字編輯工具,編輯器遇到了一個效能問題是新增多張圖片,當滾動編輯區域,遇到圖片切換的時候會有明顯的卡頓現象。這篇文章基於這個卡頓的效能問題進行效能瓶頸的分析以及做對應的優化。
可以開啟這個連結 iOS使用UITableView實現的富文字編輯器 檢視我的文章,這篇文章所用的專案也是基於這個專案的。
結果
最終的分析優化的結果把時間從90ms的數量級降低到了2ms的數量級,達到了一個比較流暢的效果。具體的分析優化步驟請往下看。
問題分析
既然問題是發生在圖片切換的時候,圖片是放在單獨的一個Cell中的,那麼就嘗試在Cell的渲染方法
cellForRowAtIndexPath
新增兩個Log,檢視方法執行所用的時間。
對應的結果:
2017-08-11 06:12:48.744 RichTextEditDemo[6867:1064632] ======begin render cell
2017-08-11 06:12:48.749 RichTextEditDemo[6867:1064632] ======end render cell
2017-08-11 06:12:49.261 RichTextEditDemo[6867:1064632] ======begin render cell
2017-08-11 06:12:49.266 RichTextEditDemo[6867:1064632] ======end render cell
複製程式碼
從日誌列印的時間上看,大概每渲染一個Cell只要發幾毫秒的時間,貌似問題不會出現在這個位置,然而這並不是真相,很明顯的,其他地方不會影響到,所以得用更高階的分析工具去分析檢視。
發現問題
Instrument是一個很好的效能分析工具,可以分析記憶體分配、記憶體洩漏、網路情況、CPU佔用等和效能有關的問題,當前的效能問題是耗時的問題,可以使用 Instrument 的 Time Profiler 進行分析
讓這個列表滾動,並且有進行圖片Cell的切換
可以看到Time Profiler 有下面的記錄,紅色框中就是Cell切換所耗費的時間值,這個時間的增長很明顯的高於其他值了,所以這個就是我們要定位到的地方了。
Tips
- alt + 滑鼠滾輪 -> 縮放時間軸
- shift + 滑鼠滾輪 -> 移動時間軸
- 按住滑鼠框選 -> 選擇和定位時間軸
第一步要在時間軸上框選一個範圍,標識選擇這個範圍進行分析,才能準確定位到這個問題,如圖(1)位置所示;第二步要選在堆疊中的某一個函式,一般的選擇到OC函式呼叫,更底層的函式呼叫就到了CF層是C語言實現的就不好分析了,所以這裡選擇的是 [UIImage drawInRect:blendMode:alpha]
這個函式分析,可以看到這個函式呼叫說花費的時間是 92ms,這是一個比較長的時間了,所以應該就是這裡導致的卡頓了。
這個函式花費的時間和image圖片的大小有關係的,選擇另一個時間峰值範圍,這個時間峰值範圍是發生在小圖之間的切換的
這個地方耗費的時間就比較小一點,不過也是達到了25ms,對於效能也是有一定的影響的。
解決問題
以上的分析可以得出結論:[UIImage drawInRect:blendMode:alpha]
函式的呼叫是會導致效能問題的,因為UITextView內部處理圖片的方式是通過呼叫 [UIImage drawInRect:blendMode:alpha]
函式繪製圖片實現的。
既然是UITextView內部的處理方式,所以這個函式呼叫行為是應用層改變不了的,不過UIImage物件是我們可以控制的,或者可以改變圖片的顯示方式來達到優化的目的,所以就有了以下的兩種方案。
方案1
第一種方案就是對預覽的圖片進行壓縮,然後再設定到NSTextAttachmen中,放到UITextView中顯示
textAttachment.image = self.image;
// ===> 修改為
// scaletoSize用於壓縮原始的圖片,textAttachment中的image物件是壓縮過後的
textAttachment.image = [self.image scaletoSize:showImageWidth];
複製程式碼
這樣修改之後大圖的滾到載入時間減少到了40ms左右
雖然減少了一半的時間,不過,40ms的時間還是比較長的,下面會繼續進行優化。
方案2
上面的方案進行了圖片的壓縮,時間的耗費還是因為 [UIImage drawInRect:blendMode:alpha]
函式的呼叫,所以有沒有一種更好的方案呢?答案是肯定的,可以把傳給UITextView的image壓縮成一個很小的,(這一步也可以不必,傳遞一個空的UIImageView物件即可,這裡設定圖片的主要原因是圖片區域需要一個編輯的游標),然後在 UITextView 所對應的圖片區域新增一個UIImageView,在UIImageView中設定原始的圖片即可,這種方案會比方案1的效果好很多。
方案二幾個修改點:
- 設定NSTextAttachment的image為空的UIImage物件
//....
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
CGRect rect = CGRectZero;
rect.size.width = showImageWidth;
rect.size.height = showImageHeight;
textAttachment.bounds = rect;
textAttachment.image = [UIImage new];
NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:textAttachment];
//....
複製程式碼
- Cell新增ImageView顯示Image
[self.imageContentView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(_imageModel.imageFrame.origin.x);
make.top.equalTo(self).offset(_imageModel.imageFrame.origin.y);
make.height.equalTo(@(_imageModel.imageFrame.size.height));
make.width.equalTo(@(_imageModel.imageFrame.size.width));
}];
複製程式碼
下面是使用方案2優化之後的分析圖
圖中可以看到
cellForRowAtIndexPath
方法總共佔用了2ms的時間,從分析的堆疊中可以看到 UITextView setAttributedText:
方法才佔用了1ms的時間,所以這個提升是很明顯的,因為傳遞了一個空的UIImageView物件,不用執行 [UIImage drawInRect:blendMode:alpha]
方法,使用了UIImageView直接設定Image的方式幾乎不會佔用時間,所以堆疊中看不到 [UIImageView setImage:]
方法呼叫的時間。
總結
Instrument是一個很好工具,你用它可以很方便的幫我們定位到效能問題,問題找到了,那麼也就很容易找到解決方案了。