老文章遷移掘金ios
整篇文章其實說來講去,最後其實只是把卡頓的這個事情用最通俗最簡單最沒技術含量的方案實行了。但那麼多方案,爲何選擇一個方案,每一個方案都有優點,一樣也有弊端,不一樣的case,不一樣的場景,不可能一個方案萬金油適用,這個過程須要咱們刨根問底,去了解簡單的解決方案背後的爲何?性能優化
在tableView
or collectionView
的Cell使用中若是大量出現了view.layer.cornerRadius
+ ClipToBounds
ormasksToBounds
的設置,會形成滾動不流暢,滾動起來十分的卡頓。app
這一點相信不少iOS developer都不陌生,相關的搜索圓角卡頓
,圓角性能優化
,都能看到不少文章,思路大體以下異步
離屏渲染
離屏渲染
會消耗太多GPU資源,可是CPU卻沒有太多的佔用最普遍的辦法就是預先用CPU,構建圓角路徑貝塞爾曲線UIBezierPath
,用原來的圖片填充進圓角路徑,得到自然的自帶圓角透明的bitmap數據UIImage
,從而直接交給GPU進行普通渲染ide
有很多的blog,以及大量的demo,都驗證了這一點,但看完後不由有幾個疑問,因而產生了今天的刨根問底函數
每一種解決方案,都是在必定得特定情景下而產生的最優解,針對CPU預繪bitmap的方案,在至關多的使用場景下,是正確的沒有任何問題的。性能
可是這裏要講一個真實的case,若是這個方案都沒法解決問題,依然仍是卡,那麼該怎麼辦?學習
到底卡頓根本緣由是啥?測試
沒法完全緩解卡頓,該怎麼辦?優化
咱們的App有一個用UICollectionView
製做了書架的功能,能夠放置圖書,多本圖書疊放在一塊兒自動生成文件夾,如圖
能夠看到圖中每一本書都是一個圓角矩形,而一個文件夾,若是圖書超過4本則是4+1個圓角矩形,普通的用戶使用習慣,可能不會有太多的文件夾(也說不許喲!)可是若是文件夾數量超過2個屏幕,每個文件夾都是4本以上的圖書,那麼在2各屏幕內來回滾動,產生了很恐怖的結果。。。
咱們的app,在ip6上,同屏幕最多能夠有9個Item,每一個Item若是都是4個以上圖書的文件夾就是5個圓角矩形,換句話說,一個屏幕內最多有45個圓角矩形。在這樣的極限狀況下,iPhone6上會卡的只有15幀左右!60幀纔是滿幀啊親。。。
更況且咱們的app,在iPhone 6 plus上是4*4個item,因而一個屏幕內最多有80個圓角矩形。
咱們還支持iPad universal
細思極恐
首先在沒有任何優化代碼的狀況下,都是最直接的
bookProfileImageView.layer.cornerRadius = 3.0f;
bookProfileImageView.clipsToBounds = YES;
複製代碼
讓咱們看一組Instrument
裏面core Animation
的數據結果
圖書範圍內滾動幀率
咱們能夠看出,在全是圖書的狀況下,僅僅9個圓角矩形,並不會影響幀率,至少能保持在55幀左右,滾動流暢度接近100% 而且Cpu的佔用率並不高
文件夾範圍內滾動幀率
可怕地事情來了,在全是文件夾的狀況下,已經達到了單屏45個圓角矩形,幀率已經降到了平均15,這是一種什麼感受,滿幀率60,如今只達到了滾動流暢的25%,簡直慘不忍睹
你們再看下Cpu佔用率,仍是不高
看一下Cpu的消耗狀況
咱們仔細看看Cpu都消耗在哪?
能夠看到,Cpu的消耗在曲線圖上並無陡然增高,消耗也都是一些基本的UICollectionView的處理
能夠看到,在極限純文件夾區域滾動的時候,就只有那4個字能夠形容慘不忍睹
這樣的產生緣由,其實在一開始就提到了,由於圓角遮罩,在Gpu運算的時候會發生離屏渲染
離屏渲染
將離屏渲染做爲關鍵詞去搜索一下你會查到不少的信息,好比iOS離屏渲染的研究
當使用圓角,陰影,遮罩的時候,圖層屬性的混合體被指定爲在未預合成以前不能直接在屏幕中繪製,因此就須要屏幕外渲染被喚起。 屏幕外渲染並不意味着軟件繪製,可是它意味着圖層必須在被顯示以前在一個屏幕外上下文中被渲染(不論Cpu仍是Gpu)。 因此當使用離屏渲染的時候會很容易形成性能消耗,由於在OPENGL裏離屏渲染會單獨在內存中建立一個屏幕外緩衝區並進行渲染,而屏幕外緩衝區跟當前屏幕緩衝區上下文切換是很耗性能的。
離屏渲染能夠是廣義的理解爲,在屏幕外的時候就要進行渲染,不管是Cpu仍是Gpu
在咱們的當前的Case裏,由於過分的使用了Gpu去處理圓角遮罩,所以Gpu發生了大量的離屏渲染,大幅度拖慢了速度,致使幀率如此悲慘
但由於是Gpu那邊的資源被過分消耗,Cpu這邊顯然處於比較悠閒的狀態,所以,這三張圖片裏面,Cpu佔用率一直不高,而且沒有明顯的某個異常函數嚴重消耗Cpu資源
那麼,咱們就對他進行必定的優化
這段代碼是引用來的,由於前一陣子不少人討論這個話題,已經有了不少優秀的方案,好比 iOS高效添加圓角實戰講解
-(void)kt_addCorner:(CGFloat)radius
{
if (self.image) {
self.image = [self.image imageAddCornerWithRadius:radius andSize:self.bounds.size];
}
return;
}
- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
CGContextAddPath(ctx,path.CGPath);
CGContextClip(ctx);
[self drawInRect:rect];
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
複製代碼
能夠看到,思路就是繪製一個圓角的路徑,而後填充生成一個天生自帶圓角的bitmap
而後注掉了代碼中全部的cornerRadius
換上了
[bookProfileImageView kt_addCorner]
複製代碼
上問題到離屏渲染能夠是廣義的理解爲,在屏幕外的時候就要進行渲染,不管是Cpu仍是Gpu
因此思路就是,既然Gpu目前負荷會比較大,而Cpu則至關空閒,那咱們何不讓Cpu提早處理一下bitmap數據呢,這也算是另外一種Cpu離屏渲染
讓咱們看看效果
讓咱們看一組Instrument
裏面core Animation
的數據結果
圖書範圍內滾動幀率
什麼鬼!圖書範圍的性能竟然降低了!! 對比前面的截圖能夠明顯看出來,圖書範圍的滾動幀率從平均55左右降低到平均50,雖然很是的細微,但很明顯,在這個優化改動下,圖書範圍滾動性能反而降低了!
仔細看一下Cpu佔用率,經過前面的圖進行對比,仍是能看出來有增高,或許不明顯,咱們繼續看
文件夾範圍內滾動幀率
你們注意看,一樣的代碼下,文件夾區域的滾動性能卻有大幅度提高,從剛纔的15幀左右提高到了35幀,提高效果超過了100%
可是咱們仔細看,Cpu佔用率此次能明顯看出提高了不少不少!
看一下Cpu的消耗狀況
咱們仔細看看Cpu都消耗在哪?
看到了吧,此時此刻,Cpu被大量消耗在了imageAddCornerWithRadius:andSize:
這個咱們專門爲優化而寫的函數裏,很明顯的說明,在Cpu的層面,此處已經在重點消耗Cpu資源了
能夠看到,咱們專門針對Gpu離屏渲染作的專門的優化,彷佛?並無那麼有效!是否是?
到底爲何會這樣呢?
簡單的來講就一句話
Gpu運算會有消耗,轉移給Cpu去運算必定也會產生消耗
爲何會是這樣,仍是得從離屏渲染下手
離屏渲染
上文提到的離屏渲染 有這樣一句話
因此當使用離屏渲染的時候會很容易形成性能消耗,由於在OPENGL裏離屏渲染會單獨在內存中建立一個屏幕外緩衝區並進行渲染,而屏幕外緩衝區跟當前屏幕緩衝區上下文切換是很耗性能的。
另一篇文章有這樣一句話 A Performance-minded take on iOS design
You’d think the GPU would always be faster than the CPU at this sort of thing, but there are some tricky considerations here. It’s expensive for the GPU to switch contexts from on-screen to off-screen drawing (it must flush its pipelines and barrier), so for simple drawing operations, the setup cost may be greater than the total cost of doing the drawing in CPU via e.g.
咱們能夠理解爲Gpu在處理浮點運算,處理矩陣運算的時候,必定會比Cpu快得,畢竟他天生就是拿來作圖形處理的,因此在離屏渲染的數量比較少的時候,咱們把運算交給Cpu,反而是略微增長了耗時與卡頓
就像引文裏說的,離屏渲染真正的消耗,在於不一樣緩衝區的來回切換,一旦圓角的數量增多,計算量加大,這種切換會更加頻繁,因此當數量龐大的時候,Gpu最終全部的操做就會更加耗時
要說明的是,咱們此次的case和網上的其餘例子比是有不一樣的
Cpu離屏渲染
,可是幀率依然只有35幀左右,還卡!怎麼辦!咱們的核心目的是,消除卡頓,感覺感覺絲般順滑,可是現有的一些手段,雖然有效果,但還遠遠達不到目標怎麼辦?
有人說了,不要切圓角了,直接讓UE出一張圓角切圖,把全部的運算都省了
那咱們來試試
去掉全部圓角代碼
//bookProfileImageView.layer.cornerRadius = 3.0f;
//bookProfileImageView.clipsToBounds = YES;
複製代碼
換上這樣的一張圖,中間透明四個角有背景色
讓咱們測試下幀率
圖書範圍內滾動幀率
文件夾範圍內滾動幀率
不管是圖書,仍是文件夾都已經達到了55幀左右的幀率,接近滿幀
絲般順滑
這樣就知足了麼?顯然是不能夠的,由於若是一旦圓角item背後有背景圖,有紋理,那這種貼圖的方式根本不能實現了。難道就這麼讓app卡着湊合用麼
顯然不能夠
解決問題應該從源頭入手,因此咱們相應地要思考,卡頓是怎麼來的?
這塊就要從ibireme大神的 iOS 保持界面流暢的技巧 這篇博客來深刻學習
圖爲原博客中的圖
iOS設備都是採用雙緩衝區+垂直同步開啓的方式來進行圖形渲染,什麼意思呢?
這幅圖更加直觀
當圖形的總運算量在那裏擺着,就是很大,就是很卡怎麼辦呢?
異步繪製
簡單地說,就是已經採用了Cpu離屏渲染,仍是會由於主線程計算耗時很長而卡頓UI,那咱們就把Cpu計算bitmap這個過程放到線程裏去。
運算量大怎麼辦?
但由於咱們面對的是頻繁複用的UICollectionView
或者UITableView
,因此要有很完善的線程管理機制,再輔助以cache機制
採用圖片的方法已經解決了當下app的卡頓問題,可是後續對AsyncDisplay的支持,等有空了再整理一篇吧。。
其實 facebook開源的 AsyncDisplayKit 就是實現了這些,功能很強大,我還沒用熟,感受有點重