本文是投稿文章,作者:空之境界(博客)
在iOS的世界,圓角無處不在,而且必須存在。因為圓角是符合人類視覺安全體驗的,圓角讓人覺得舒適,而方角在潛意識層次是具有傷害體驗的,因為尖尖的東西總是有可能對人造成傷害的,所以我們更喜歡圓角。在我之前的文章中講過,在iOS的中設置圓角是非常容易的一件事情,這也體現出蘋果也是非常重視圓角這件事情的。
圓角雖好,但如果使用不當,它就是你的幀數殺手,特別當它出現在滾動列表的時候。下面來看圓角如何毀掉你的流暢度的。
實測
layer.cornerRadius
我創建了一個簡單地UITableView視圖,為每個cell添加了2個UIImageView實例,且為UIImageView實例進行如下設置
aImageView.layer.cornerRadius = aImageView.frame.size.width/2.0; aImageView.layer.masksToBounds = YES;
運行截圖如下:
你們猜,現在滾動的幀率是多少。
已經跌至45幀每秒,這個幀率已經讓人感覺到不那麼順滑了,如果低於40幀每秒,普通用戶就會察覺明顯的不流暢了。當我把cell的UIImageView實例增加至四個
現在幀率已經低於30幀每秒了
這個幀率如果出現在首屏,足以引領你的app進入垃圾級別的體驗了。 現在我把UIImageView實例的size調的小一些。
平均幀率提高了大概3幀每秒。
在這裡視圖和圓角的大小對幀率並沒有什麼卵影響,數量才是傷害的核心輸出啊。
layer.mask
之前有的文章說通過layer.cornerRadius和layer.mask設置圓角並沒有什麼差異,事實真的是這樣的嗎?我如下設置了圓角:
CAShapeLayer *layer = [CAShapeLayer layer]; UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:aImageView.bounds]; layer.path = aPath.CGPath; aImageView.layer.mask = layer;
得到的幀率如下:
竟然只有20幀每秒了,比layer.cornerRadius還少了8幀!!!所以layer.cornerRadius實現圓角的性能是要比layer.mask要高很多。
maskView
iOS的UIView多了一個maskView方法,不過這個東西和layer.mask是一個卵樣的。
原理
上面拖慢幀率的原因其實都是Off-Screen Rendering(離屏渲染)的原因。離屏渲染是個好東西,但是頻繁發生離屏渲染是非常耗時的。
Off-Screen Rendering
離屏渲染,指的是GPU在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作。由上面的一個結論視圖和圓角的大小對幀率並沒有什麼卵影響,數量才是傷害的核心輸出啊。可以知道離屏渲染耗時是發生在離屏這個動作上面,而不是渲染。為什麼離屏這麼耗時?原因主要有創建緩沖區和上下文切換。創建新的緩沖區代價都不算大,付出最大代價的是上下文切換。
上下文切換
上下文切換,不管是在GPU渲染過程中,還是一直所熟悉的進程切換,上下文切換在哪裡都是一個相當耗時的操作。首先我要保存當前屏幕渲染環境,然後切換到一個新的繪制環境,申請繪制資源,初始化環境,然後開始一個繪制,繪制完畢後銷毀這個繪制環境,如需要切換到On-Screen Rendering或者再開始一個新的離屏渲染重復之前的操作。 下圖描述了一次mask的渲染操作。
一次mask發生了兩次離屏渲染和一次主屏渲染。即使忽略昂貴的上下文切換,一次mask需要渲染三次才能在屏幕上顯示,這已經是普通視圖顯示3陪耗時,若再加上下文環境切換,一次mask就是普通渲染的30倍以上耗時操作。問我這個30倍以上這個數據怎麼的出來的?當我在cell的UIImageView的實例增加到150個,並去掉圓角的時候,幀數才跌至28幀每秒。雖然不是甚准確,但至少反映mask這個耗時是無mask操作的耗時的數十倍的。
應對
那麼如何應對這個問題呢?不要在滾動視圖使用cornerRadius或者mask。如果你非要作死怎麼辦呢?那麼這樣也可以拯救你:
self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [UIScreen mainScreen].scale;
這樣大部分情況下可以馬上挽救你的幀數在55幀每秒以上。shouldRasterize = YES會使視圖渲染內容被緩存起來,下次繪制的時候可以直接顯示緩存,當然要在視圖內容不改變的情況下。
除了上面非要作死的人外,大家還是采取預先生成圓角圖片,並緩存起來這個方法才是比較好的手段。預處理圓角圖片可以在後台處理,處理完畢後緩存起來,再在主線程顯示,這就避免了不必要的離屏渲染了。
另外也有在圖片上面覆蓋一個镂空圓形圖片的方法可以實現圓形頭像效果,這個也是極為高效的方法。缺點就是對視圖的背景有要求,單色背景效果就最為理想。
總結
實現圓角cornerRadius要比mask高效很多。
Rasterize在大部分情況下極大減少GPU工作。在有空間的情況下,大部分情況下緩存總能幫到你,不是嗎?
後台預處理圖片也能很簡單幫上你很大的忙。