精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

如何讓iOS保持界面流暢?這些技巧你知道嗎

移動開發
這篇文章會非常詳細的分析 iOS 界面構建中的各種性能問題以及對應的解決思路,同時給出一個開源的微博列表實現,通過實際的代碼展示如何構建流暢的交互。

[[157616]]

這篇文章會非常詳細的分析 iOS 界面構建中的各種性能問題以及對應的解決思路,同時給出一個開源的微博列表實現,通過實際的代碼展示如何構建流暢的交互。

Index

1.演示項目

2.屏幕顯示圖像的原理

3.卡頓產生的原因和解決方案

  • CPU 資源消耗原因和解決方案

  • GPU 資源消耗原因和解決方案

4.AsyncDisplayKit

  • ASDK 的由來

  • ASDK 的資料

  • ASDK 的基本原理

  • ASDK 的圖層預合成

  • ASDK 異步并發操作

  • Runloop 任務分發

5.微博 Demo 性能優化技巧

  • 預排版

  • 預渲染

  • 異步繪制

  • 全局并發控制

  • 更高效的異步圖片加載

  • 其他可以改進的地方

6.如何評測界面的流暢度

演示項目

在開始技術討論前,你可以先下載我寫的 Demo 跑到真機上體驗一下:https://github.com/ibireme/YYKit。 Demo 里包含一個微博的 Feed 列表、發布視圖,還包含一個 Twitter 的 Feed 列表。為了公平起見,所有界面和交互我都從官方應用原封不動的抄了過來,數據也都是從官方應用抓取的。你也可以自己抓取數據替換掉 Demo 中的數據,方便進行對比。盡管官方應用背后的功能更多更為復雜,但不至于會帶來太大的交互性能差異。

QQ截圖20151127145359.png

這個 Demo 最低可以運行在 iOS 6 上,所以你可以把它跑到老設備上體驗一下。在我的測試中,即使在 iPhone 4S 或者 iPad 3 上,Demo 列表在快速滑動時仍然能保持 50~60 FPS 的流暢交互,而其他諸如微博、朋友圈等應用的列表視圖在滑動時已經有很嚴重的卡頓了。

微博的 Demo 有大約四千行代碼,Twitter 的只有兩千行左右代碼,第三方庫只用到了 YYKit,文件數量比較少,方便查看。好了,下面是正文。

屏幕顯示圖像的原理

QQ截圖20151127145420.png

首先從過去的 CRT 顯示器原理說起。CRT 的電子槍按照上面方式,從上到下一行行掃描,掃描完成后顯示器就呈現一幀畫面,隨后電子槍回到初始位置繼續下一次掃描。為了把顯示器的顯示過程和系統的視頻控制器進行同步,顯示器(或者其他硬件)會用硬件時鐘產生一系列的定時信號。當電子槍換到新的一行,準備進行掃描時,顯示器會發出一個水平同步信號(horizonal synchronization),簡稱 HSync;而當一幀畫面繪制完成后,電子槍回復到原位,準備畫下一幀前,顯示器會發出一個垂直同步信號(vertical synchronization),簡稱 VSync。顯示器通常以固定頻率進行刷新,這個刷新率就是 VSync 信號產生的頻率。盡管現在的設備大都是液晶顯示屏了,但原理仍然沒有變。

ios_screen_display.png

通常來說,計算機系統中 CPU、GPU、顯示器是以上面這種方式協同工作的。CPU 計算好顯示內容提交到 GPU,GPU 渲染完成后將渲染結果放入幀緩沖區,隨后視頻控制器會按照 VSync 信號逐行讀取幀緩沖區的數據,經過可能的數模轉換傳遞給顯示器顯示。

在最簡單的情況下,幀緩沖區只有一個,這時幀緩沖區的讀取和刷新都都會有比較大的效率問題。為了解決效率問題,顯示系統通常會引入兩個緩沖區,即雙緩沖機制。在這種情況下,GPU 會預先渲染好一幀放入一個緩沖區內,讓視頻控制器讀取,當下一幀渲染好后,GPU 會直接把視頻控制器的指針指向第二個緩沖器。如此一來效率會有很大的提升。

雙緩沖雖然能解決效率問題,但會引入一個新的問題。當視頻控制器還未讀取完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩沖區并把兩個緩沖區進行交換后,視頻控制器就會把新的一幀數據的下半段顯示到屏幕上,造成畫面撕裂現象,如下圖:

ios_vsync_off.jpg

為了解決這個問題,GPU 通常有一個機制叫做垂直同步(簡寫也是 V-Sync),當開啟垂直同步后,GPU 會等待顯示器的 VSync 信號發出后,才進行新的一幀渲染和緩沖區更新。這樣能解決畫面撕裂現象,也增加了畫面流暢度,但需要消費更多的計算資源,也會帶來部分延遲。

那么目前主流的移動設備是什么情況呢?從網上查到的資料可以知道,iOS 設備會始終使用雙緩存,并開啟垂直同步。而安卓設備直到 4.1 版本,Google 才開始引入這種機制,目前安卓系統是三緩存+垂直同步。

卡頓產生的原因和解決方案

ios_frame_drop.png

在 VSync 信號到來后,系統圖形服務會通過 CADisplayLink 等機制通知 App,App 主線程開始在 CPU 中計算顯示內容,比如視圖的創建、布局計算、圖片解碼、文本繪制等。隨后 CPU 會將計算好的內容提交到 GPU 去,由 GPU 進行變換、合成、渲染。隨后 GPU 會把渲染結果提交到幀緩沖區去,等待下一次 VSync 信號到來時顯示到屏幕上。由于垂直同步的機制,如果在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內容不變。這就是界面卡頓的原因。

從上面的圖中可以看到,CPU 和 GPU 不論哪個阻礙了顯示流程,都會造成掉幀現象。所以開發時,也需要分別對 CPU 和 GPU 壓力進行評估和優化。

CPU 資源消耗原因和解決方案

對象創建

對象的創建會分配內存、調整屬性、甚至還有讀取文件等操作,比較消耗 CPU 資源。盡量用輕量的對象代替重量的對象,可以對性能有所優化。比如 CALayer 比 UIView 要輕量許多,那么不需要響應觸摸事件的控件,用 CALayer 顯示會更加合適。如果對象不涉及 UI 操作,則盡量放到后臺線程去創建,但可惜的是包含有 CALayer 的控件,都只能在主線程創建和操作。通過 Storyboard 創建視圖對象時,其資源消耗會比直接通過代碼創建對象要大非常多,在性能敏感的界面里,Storyboard 并不是一個好的技術選擇。

盡量推遲對象創建的時間,并把對象的創建分散到多個任務中去。盡管這實現起來比較麻煩,并且帶來的優勢并不多,但如果有能力做,還是要盡量嘗試一下。如果對象可以復用,并且復用的代價比釋放、創建新對象要小,那么這類對象應當盡量放到一個緩存池里復用。

對象調整

對象的調整也經常是消耗 CPU 資源的地方。這里特別說一下 CALayer:CALayer 內部并沒有屬性,當調用屬性方法時,它內部是通過運行時 resolveInstanceMethod 為對象臨時添加一個方法,并把對應屬性值保存到內部的一個 Dictionary 里,同時還會通知 delegate、創建動畫等等,非常消耗資源。UIView 的關于顯示相關的屬性(比如 frame/bounds/transform)等實際上都是 CALayer 屬性映射來的,所以對 UIView 的這些屬性進行調整時,消耗的資源要遠大于一般的屬性。對此你在應用中,應該盡量減少不必要的屬性修改。

當視圖層次調整時,UIView、CALayer 之間會出現很多方法調用與通知,所以在優化性能時,應該盡量避免調整視圖層次、添加和移除視圖。

對象銷毀

對象的銷毀雖然消耗資源不多,但累積起來也是不容忽視的。通常當容器類持有大量對象時,其銷毀時的資源消耗就非常明顯。同樣的,如果對象可以放到后臺線程去釋放,那就挪到后臺線程去。這里有個小 Tip:把對象捕獲到 block 中,然后扔到后臺隊列去隨便發送個消息以避免編譯器警告,就可以讓對象在后臺線程銷毀了。

  1. NSArray *tmp = self.array; 
  2. self.array = nil; 
  3. dispatch_async(queue, ^{ 
  4.     [tmp class]; 
  5. }); 

布局計算

視圖布局的計算是 App 中最為常見的消耗 CPU 資源的地方。如果能在后臺線程提前計算好視圖布局、并且對視圖布局進行緩存,那么這個地方基本就不會產生性能問題了。

不論通過何種技術對視圖進行布局,其最終都會落到對 UIView.frame/bounds/center 等屬性的調整上。上面也說過,對這些屬性的調整非常消耗資源,所以盡量提前計算好布局,在需要時一次性調整好對應屬性,而不要多次、頻繁的計算和調整這些屬性。

Autolayout

Autolayout 是蘋果本身提倡的技術,在大部分情況下也能很好的提升開發效率,但是 Autolayout 對于復雜視圖來說常常會產生嚴重的性能問題。隨著視圖數量的增長,Autolayout 帶來的 CPU 消耗會呈指數級上升。具體數據可以看這個文章:http://pilky.me/36/。 如果你不想手動調整 frame 等屬性,你可以用一些工具方法替代(比如常見的 left/right/top/bottom/width/height 快捷屬性),或者使用 ComponentKit、AsyncDisplayKit 等框架。

文本計算

如果一個界面中包含大量文本(比如微博微信朋友圈等),文本的寬高計算會占用很大一部分資源,并且不可避免。如果你對文本顯示沒有特殊要求,可以參考下 UILabel 內部的實現方式:用 [NSAttributedString boundingRectWithSize:options:context:] 來計算文本寬高,用 -[NSAttributedString drawWithRect:options:context:] 來繪制文本。盡管這兩個方法性能不錯,但仍舊需要放到后臺線程進行以避免阻塞主線程。

如果你用 CoreText 繪制文本,那就可以先生成 CoreText 排版對象,然后自己計算了,并且 CoreText 對象還能保留以供稍后繪制使用。

文本渲染

屏幕上能看到的所有文本內容控件,包括 UIWebView,在底層都是通過 CoreText 排版、繪制為 Bitmap 顯示的。常見的文本控件 (UILabel、UITextView 等),其排版和繪制都是在主線程進行的,當顯示大量文本時,CPU 的壓力會非常大。對此解決方案只有一個,那就是自定義文本控件,用 TextKit 或最底層的 CoreText 對文本異步繪制。盡管這實現起來非常麻煩,但其帶來的優勢也非常大,CoreText 對象創建好后,能直接獲取文本的寬高等信息,避免了多次計算(調整 UILabel 大小時算一遍、UILabel 繪制時內部再算一遍);CoreText 對象占用內存較少,可以緩存下來以備稍后多次渲染。

圖片的解碼

當你用 UIImage 或 CGImageSource 的那幾個方法創建圖片時,圖片數據并不會立刻解碼。圖片設置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的數據才會得到解碼。這一步是發生在主線程的,并且不可避免。如果想要繞開這個機制,常見的做法是在后臺線程先把圖片繪制到 CGBitmapContext 中,然后從 Bitmap 直接創建圖片。目前常見的網絡圖片庫都自帶這個功能。

圖像的繪制

圖像的繪制通常是指用那些以 CG 開頭的方法把圖像繪制到畫布中,然后從畫布創建圖片并顯示這樣一個過程。這個最常見的地方就是 [UIView drawRect:] 里面了。由于 CoreGraphic 方法通常都是線程安全的,所以圖像的繪制可以很容易的放到后臺線程進行。一個簡單異步繪制的過程大致如下(實際情況會比這個復雜得多,但原理基本一致):

  1. - (void)display { 
  2.     dispatch_async(backgroundQueue, ^{ 
  3.         CGContextRef ctx = CGBitmapContextCreate(...); 
  4.         // draw in context... 
  5.         CGImageRef img = CGBitmapContextCreateImage(ctx); 
  6.         CFRelease(ctx); 
  7.         dispatch_async(mainQueue, ^{ 
  8.             layer.contents = img; 
  9.         }); 
  10.     }); 

GPU 資源消耗原因和解決方案

相對于 CPU 來說,GPU 能干的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形),應用變換(transform)、混合并渲染,然后輸出到屏幕上。通常你所能看到的內容,主要也就是紋理(圖片)和形狀(三角模擬的矢量圖形)兩類。

紋理的渲染

所有的 Bitmap,包括圖片、文本、柵格化的內容,最終都要由內存提交到顯存,綁定為 GPU Texture。不論是提交到顯存的過程,還是 GPU 調整和渲染 Texture 的過程,都要消耗不少 GPU 資源。當在較短時間顯示大量圖片時(比如 TableView 存在非常多的圖片并且快速滑動時),CPU 占用率很低,GPU 占用非常高,界面仍然會掉幀。避免這種情況的方法只能是盡量減少在短時間內大量圖片的顯示,盡可能將多張圖片合成為一張進行顯示。

當圖片過大,超過 GPU 的最大紋理尺寸時,圖片需要先由 CPU 進行預處理,這對 CPU 和 GPU 都會帶來額外的資源消耗。目前來說,iPhone 4S 以上機型,紋理尺寸上限都是 4096x4096,更詳細的資料可以看這里:iosres.com。所以,盡量不要讓圖片和視圖的大小超過這個值。

視圖的混合 (Composing)

當多個視圖(或者說 CALayer)重疊在一起顯示時,GPU 會首先把他們混合到一起。如果視圖結構過于復雜,混合的過程也會消耗很多 GPU 資源。為了減輕這種情況的 GPU 消耗,應用應當盡量減少視圖數量和層次,并在不透明的視圖里標明 opaque 屬性以避免無用的 Alpha 通道合成。當然,這也可以用上面的方法,把多個視圖預先渲染為一張圖片來顯示。

圖形的生成

CALayer 的 border、圓角、陰影、遮罩(mask),CASharpLayer 的矢量圖形顯示,通常會觸發離屏渲染(offscreen rendering),而離屏渲染通常發生在 GPU 中。當一個列表視圖中出現大量圓角的 CALayer,并且快速滑動時,可以觀察到 GPU 資源已經占滿,而 CPU 資源消耗很少。這時界面仍然能正常滑動,但平均幀數會降到很低。為了避免這種情況,可以嘗試開啟 CALayer.shouldRasterize 屬性,但這會把原本離屏渲染的操作轉嫁到 CPU 上去。對于只需要圓角的某些場合,也可以用一張已經繪制好的圓角圖片覆蓋到原本視圖上面來模擬相同的視覺效果。最徹底的解決辦法,就是把需要顯示的圖形在后臺線程繪制為圖片,避免使用圓角、陰影、遮罩等屬性。

AsyncDisplayKit

AsyncDisplayKit 是 Facebook 開源的一個用于保持 iOS 界面流暢的庫,我從中學到了很多東西,所以下面我會花較大的篇幅來對其進行介紹和分析。

ASDK 的由來

[[157621]]

ASDK 的作者是 Scott Goodson (Linkedin),他曾經在蘋果工作,負責 iOS 的一些內置應用的開發,比如股票、計算器、地圖、鐘表、設置以及Safari 等,當然他也參與了 UIKit framework 的開發。后來他加入 Facebook 后,負責 Paper 的開發,創建并開源了 AsyncDisplayKit。目前他在 Pinterest 和 Instagram 負責 iOS 開發和用戶體驗的提升等工作。

asdk_history.png

ASDK 自 2014 年 6 月開源,10 月發布 1.0 版。目前 ASDK 即將要發布 2.0 版。
V2.0 增加了更多布局相關的代碼,ComponentKit 團隊為此貢獻很多。
現在 Github 的 master 分支上的版本是 V1.9.1,已經包含了 V2.0 的全部內容。

ASDK 的資料

想要了解 ASDK 的原理和細節,最好從下面幾個視頻開始:

2014.10.15 NSLondon - Scott Goodson - Behind AsyncDisplayKit

2015.03.02 MCE 2015 - Scott Goodson - Effortless Responsiveness with AsyncDisplayKit

2015.10.25 AsyncDisplayKit 2.0: Intelligent User Interfaces - NSSpain 2015

前兩個視頻內容大同小異,都是介紹 ASDK 的基本原理,附帶介紹 POP 等其他項目。
后一個視頻增加了 ASDK 2.0 的新特性的介紹。

除此之外,還可以到 Github Issues 里看一下 ASDK 相關的討論,下面是幾個比較重要的內容:

關于 Runloop Dispatch

關于 ComponentKit 和 ASDK 的區別

為什么不支持 Storyboard 和 Autolayout

如何評測界面的流暢度

之后,還可以到 Google Groups 來查看和討論更多內容:
https://groups.google.com/forum/#!forum/asyncdisplaykit

ASDK 的基本原理

asdk_design.png

ASDK 認為,阻塞主線程的任務,主要分為上面這三大類。文本和布局的計算、渲染、解碼、繪制都可以通過各種方式異步執行,但 UIKit 和 Core Animation 相關操作必需在主線程進行。ASDK 的目標,就是盡量把這些任務從主線程挪走,而挪不走的,就盡量優化性能。
為了達成這一目標,ASDK 嘗試對 UIKit 組件進行封裝:

asdk_layer_backed_view.png

這是常見的 UIView 和 CALayer 的關系:View 持有 Layer 用于顯示,View 中大部分顯示屬性實際是從 Layer 映射而來;Layer 的 delegate 在這里是 View,當其屬性改變、動畫產生時,View 能夠得到通知。UIView 和 CALayer 不是線程安全的,并且只能在主線程創建、訪問和銷毀。

asdk_view_backed_node.png

ASDK 為此創建了 ASDisplayNode 類,包裝了常見的視圖屬性(比如 frame/bounds/alpha/transform/backgroundColor/superNode/subNodes 等),然后它用 UIView->CALayer 相同的方式,實現了 ASNode->UIView 這樣一個關系。

asdk_layer_backed_node.png

當不需要響應觸摸事件時,ASDisplayNode 可以被設置為 layer backed,即 ASDisplayNode 充當了原來 UIView 的功能,節省了更多資源。

與 UIView 和 CALayer 不同,ASDisplayNode 是線程安全的,它可以在后臺線程創建和修改。Node 剛創建時,并不會在內部新建 UIView 和 CALayer,直到第一次在主線程訪問 view 或 layer 屬性時,它才會在內部生成對應的對象。當它的屬性(比如frame/transform)改變后,它并不會立刻同步到其持有的 view 或 layer 去,而是把被改變的屬性保存到內部的一個中間變量,稍后在需要時,再通過某個機制一次性設置到內部的 view 或 layer。

通過模擬和封裝 UIView/CALayer,開發者可以把代碼中的 UIView 替換為 ASNode,很大的降低了開發和學習成本,同時能獲得 ASDK 底層大量的性能優化。為了方便使用, ASDK 把大量常用控件都封裝成了 ASNode 的子類,比如 Button、Control、Cell、Image、ImageView、Text、TableView、CollectionView 等。利用這些控件,開發者可以盡量避免直接使用 UIKit 相關控件,以獲得更完整的性能提升。

ASDK 的圖層預合成

asdk_comoose_1.pngasdk_compose_2.png

有時一個 layer 會包含很多 sub-layer,而這些 sub-layer 并不需要響應觸摸事件,也不需要進行動畫和位置調整。ASDK 為此實現了一個被稱為 pre-composing 的技術,可以把這些 sub-layer 合成渲染為一張圖片。開發時,ASNode 已經替代了 UIView 和 CALayer;直接使用各種 Node 控件并設置為 layer backed 后,ASNode 甚至可以通過預合成來避免創建內部的 UIView 和 CALayer。

通過這種方式,把一個大的層級,通過一個大的繪制方法繪制到一張圖上,性能會獲得很大提升。CPU 避免了創建 UIKit 對象的資源消耗,GPU 避免了多張 texture 合成和渲染的消耗,更少的 bitmap 也意味著更少的內存占用。

ASDK 異步并發操作

[[157624]]

自 iPhone 4S 起,iDevice 已經都是雙核 CPU 了,現在的 iPad 甚至已經更新到 3 核了。充分利用多核的優勢、并發執行任務對保持界面流暢有很大作用。ASDK 把布局計算、文本排版、圖片/文本/圖形渲染等操作都封裝成較小的任務,并利用 GCD 異步并發執行。如果開發者使用了 ASNode 相關的控件,那么這些并發操作會自動在后臺進行,無需進行過多配置。

Runloop 任務分發

Runloop work distribution 是 ASDK 比較核心的一個技術,ASDK 的介紹視頻和文檔中都沒有詳細展開介紹,所以這里我會多做一些分析。如果你對 Runloop 還不太了解,可以看一下我之前的文章 深入理解RunLoop,里面對 ASDK 也有所提及。

ios_vsync_runloop.png

iOS 的顯示系統是由 VSync 信號驅動的,VSync 信號由硬件時鐘生成,每秒鐘發出 60 次(這個值取決設備硬件,比如 iPhone 真機上通常是 59.97)。iOS 圖形服務接收到 VSync 信號后,會通過 IPC 通知到 App 內。App 的 Runloop 在啟動后會注冊對應的 CFRunLoopSource 通過 mach_port 接收傳過來的時鐘信號通知,隨后 Source 的回調會驅動整個 App 的動畫與顯示。

Core Animation 在 RunLoop 中注冊了一個 Observer,監聽了 BeforeWaiting 和 Exit 事件。這個 Observer 的優先級是 2000000,低于常見的其他 Observer。當一個觸摸事件到來時,RunLoop 被喚醒,App 中的代碼會執行一些操作,比如創建和調整視圖層級、設置 UIView 的 frame、修改 CALayer 的透明度、為視圖添加一個動畫;這些操作最終都會被 CALayer 捕獲,并通過 CATransaction 提交到一個中間狀態去(CATransaction 的文檔略有提到這些內容,但并不完整)。當上面所有操作結束后,RunLoop 即將進入休眠(或者退出)時,關注該事件的 Observer 都會得到通知。這時 CA 注冊的那個 Observer 就會在回調中,把所有的中間狀態合并提交到 GPU 去顯示;如果此處有動畫,CA 會通過 DisplayLink 等機制多次觸發相關流程。

ASDK 在此處模擬了 Core Animation 的這個機制:所有針對 ASNode 的修改和提交,總有些任務是必需放入主線程執行的。當出現這種任務時,ASNode 會把任務用 ASAsyncTransaction(Group) 封裝并提交到一個全局的容器去。ASDK 也在 RunLoop 中注冊了一個 Observer,監視的事件和 CA 一樣,但優先級比 CA 要低。當 RunLoop 進入休眠前、CA 處理完事件后,ASDK 就會執行該 loop 內提交的所有任務。具體代碼見這個文件:ASAsyncTransactionGroup

通過這種機制,ASDK 可以在合適的機會把異步、并發的操作同步到主線程去,并且能獲得不錯的性能。

其他

ASDK 中還有封裝很多高級的功能,比如滑動列表的預加載、V2.0添加的新的布局模式等。ASDK 是一個很龐大的庫,它本身并不推薦你把整個 App 全部都改為 ASDK 驅動,把最需要提升交互性能的地方用 ASDK 進行優化就足夠了。

微博 Demo 性能優化技巧

我為了演示 YYKit 的功能,實現了微博和 Twitter 的 Demo,并為它們做了不少性能優化,下面就是優化時用到的一些技巧。

預排版

當獲取到 API JSON 數據后,我會把每條 Cell 需要的數據都在后臺線程計算并封裝為一個布局對象 CellLayout。CellLayout 包含所有文本的 CoreText 排版結果、Cell 內部每個控件的高度、Cell 的整體高度。每個 CellLayout 的內存占用并不多,所以當生成后,可以全部緩存到內存,以供稍后使用。這樣,TableView 在請求各個高度函數時,不會消耗任何多余計算量;當把 CellLayout 設置到 Cell 內部時,Cell 內部也不用再計算布局了。

對于通常的 TableView 來說,提前在后臺計算好布局結果是非常重要的一個性能優化點。為了達到最高性能,你可能需要犧牲一些開發速度,不要用 Autolayout 等技術,少用 UILabel 等文本控件。但如果你對性能的要求并不那么高,可以嘗試用 TableView 的預估高度的功能,并把每個 Cell 高度緩存下來。這里有個來自百度知道團隊的開源項目可以很方便的幫你實現這一點:FDTemplateLayoutCell

預渲染

微博的頭像在某次改版中換成了圓形,所以我也跟進了一下。當頭像下載下來后,我會在后臺線程將頭像預先渲染為圓形并單獨保存到一個 ImageCache 中去。

對于 TableView 來說,Cell 內容的離屏渲染會帶來較大的 GPU 消耗。在 Twitter Demo 中,我為了圖省事兒用到了不少 layer 的圓角屬性,你可以在低性能的設備(比如 iPad 3)上快速滑動一下這個列表,能感受到雖然列表并沒有較大的卡頓,但是整體的平均幀數降了下來。用 Instument 查看時能夠看到 GPU 已經滿負荷運轉,而 CPU 卻比較清閑。為了避免離屏渲染,你應當盡量避免使用 layer 的 border、corner、shadow、mask 等技術,而盡量在后臺線程預先繪制好對應內容。

異步繪制

我只在顯示文本的控件上用到了異步繪制的功能,但效果很不錯。我參考 ASDK 的原理,實現了一個簡單的異步繪制控件。這塊代碼我單獨提取出來,放到了這里:YYAsyncLayer。YYAsyncLayer 是 CALayer 的子類,當它需要顯示內容(比如調用了 [layer setNeedDisplay])時,它會向 delegate,也就是 UIView 請求一個異步繪制的任務。在異步繪制時,Layer 會傳遞一個BOOL(^isCancelled)() 這樣的 block,繪制代碼可以隨時調用該 block 判斷繪制任務是否已經被取消。

當 TableView 快速滑動時,會有大量異步繪制任務提交到后臺線程去執行。但是有時滑動速度過快時,繪制任務還沒有完成就可能已經被取消了。如果這時仍然繼續繪制,就會造成大量的 CPU 資源浪費,甚至阻塞線程并造成后續的繪制任務遲遲無法完成。我的做法是盡量快速、提前判斷當前繪制任務是否已經被取消;在繪制每一行文本前,我都會調用 isCancelled() 來進行判斷,保證被取消的任務能及時退出,不至于影響后續操作。

目前有些第三方微博客戶端(比如 VVebo、墨客等),使用了一種方式來避免高速滑動時 Cell 的繪制過程,相關實現見這個項目:VVeboTableViewDemo。它的原理是,當滑動時,松開手指后,立刻計算出滑動停止時 Cell 的位置,并預先繪制那個位置附近的幾個 Cell,而忽略當前滑動中的 Cell。這個方法比較有技巧性,并且對于滑動性能來說提升也很大,唯一的缺點就是快速滑動中會出現大量空白內容。如果你不想實現比較麻煩的異步繪制但又想保證滑動的流暢性,這個技巧是個不錯的選擇。

全局并發控制

當我用 concurrent queue 來執行大量繪制任務時,偶爾會遇到這種問題:

QQ截圖20151127153208.png

大量的任務提交到后臺隊列時,某些任務會因為某些原因(此處是 CGFont 鎖)被鎖住導致線程休眠,或者被阻塞,concurrent queue 隨后會創建新的線程來執行其他任務。當這種情況變多時,或者 App 中使用了大量 concurrent queue 來執行較多任務時,App 在同一時刻就會存在幾十個線程同時運行、創建、銷毀。CPU 是用時間片輪轉來實現線程并發的,盡管 concurrent queue 能控制線程的優先級,但當大量線程同時創建運行銷毀時,這些操作仍然會擠占掉主線程的 CPU 資源。ASDK 有個 Feed 列表的 Demo:SocialAppLayout,當列表內 Cell 過多,并且非常快速的滑動時,界面仍然會出現少量卡頓,我謹慎的猜測可能與這個問題有關。

使用 concurrent queue 時不可避免會遇到這種問題,但使用 serial queue 又不能充分利用多核 CPU 的資源。我寫了一個簡單的工具 YYDispatchQueuePool,為不同優先級創建和 CPU 數量相同的 serial queue,每次從 pool 中獲取 queue 時,會輪詢返回其中一個 queue。我把 App 內所有異步操作,包括圖像解碼、對象釋放、異步繪制等,都按優先級不同放入了全局的 serial queue 中執行,這樣盡量避免了過多線程導致的性能問題。

更高效的異步圖片加載

SDWebImage 在這個 Demo 里仍然會產生少量性能問題,并且有些地方不能滿足我的需求,所以我自己實現了一個性能更高的圖片加載庫。在顯示簡單的單張圖片時,利用 UIView.layer.contents 就足夠了,沒必要使用 UIImageView 帶來額外的資源消耗,為此我在 CALayer 上添加了 setImageWithURL 等方法。除此之外,我還把圖片解碼等操作通過 YYDispatchQueuePool 進行管理,控制了 App 總線程數量。

其他可以改進的地方

上面這些優化做完后,微博 Demo 已經非常流暢了,但在我的設想中,仍然有一些進一步優化的技巧,但限于時間和精力我并沒有實現,下面簡單列一下:

列表中有不少視覺元素并不需要觸摸事件,這些元素可以用 ASDK 的圖層合成技術預先繪制為一張圖。

再進一步減少每個 Cell 內圖層的數量,用 CALayer 替換掉 UIView。

目前每個 Cell 的類型都是相同的,但顯示的內容卻各部一樣,比如有的 Cell 有圖片,有的 Cell 里是卡片。把 Cell 按類型劃分,進一步減少 Cell 內不必要的視圖對象和操作,應該能有一些效果。

把需要放到主線程執行的任務劃分為足夠小的塊,并通過 Runloop 來進行調度,在每個 Loop 里判斷下一次 VSync 的時間,并在下次 VSync 到來前,把當前未執行完的任務延遲到下一個機會去。這個只是我的一個設想,并不一定能實現或起作用。

如何評測界面的流暢度

最后還是要提一下,“過早的優化是萬惡之源”,在需求未定,性能問題不明顯時,沒必要嘗試做優化,而要盡量正確的實現功能。做性能優化時,也最好是走修改代碼 -> Profile -> 修改代碼這樣一個流程,優先解決最值得優化的地方。

如果你需要一個明確的 FPS 指示器,可以嘗試一下 KMCGeigerCounter。對于 CPU 的卡頓,它可以通過內置的 CADisplayLink 檢測出來;對于 GPU 帶來的卡頓,它用了一個 1x1 的 SKView 來進行監視。這個項目有兩個小問題:SKView 雖然能監視到 GPU 的卡頓,但引入 SKView 本身就會對 CPU/GPU 帶來額外的一點的資源消耗;這個項目在 iOS 9 下有一些兼容問題,需要稍作調整。

我自己也寫了個簡單的 FPS 指示器:FPSLabel 只有幾十行代碼,僅用到了 CADisplayLink 來監視 CPU 的卡頓問題。雖然不如上面這個工具完善,但日常使用沒有太大問題。

最后,用 Instuments 的 GPU Driver 預設,能夠實時查看到 CPU 和 GPU 的資源消耗。在這個預設內,你能查看到幾乎所有與顯示有關的數據,比如 Texture 數量、CA 提交的頻率、GPU 消耗等,在定位界面卡頓的問題時,這是最好的工具。

責任編輯:倪明 來源: ibireme的博客
相關推薦

2020-10-28 11:20:55

vue項目技

2015-06-29 09:06:51

2020-12-24 15:26:07

Redis數據庫

2020-11-18 07:52:08

2023-10-30 09:10:05

DjangoQuerySet

2015-07-03 11:20:41

編程學習方法

2024-02-26 08:19:00

WebSpring容器

2016-01-11 09:48:07

2021-08-05 18:21:29

Autowired代碼spring

2023-01-16 08:09:51

SpringMVC句柄

2018-05-11 15:53:59

2021-04-06 11:30:50

疫情IT架構CIO

2016-03-18 19:03:35

認知計算IBM

2018-08-07 09:29:35

數據庫MySQL優化方法

2021-11-10 15:37:49

Go源碼指令

2022-11-10 09:00:41

2017-11-09 15:29:21

CPU溫度常識

2019-07-08 10:18:38

MPLSIP數據

2018-02-06 09:40:25

PythonOOP繼承

2018-03-07 06:37:14

開源項目源代碼云計算
點贊
收藏

51CTO技術棧公眾號

久久久.www| 国产a一区二区| 中文字幕国产综合| 3d欧美精品动漫xxxx无尽| 久久久精品中文字幕麻豆发布| 欧美最猛性xxxxx免费| 亚洲自拍偷拍图| 国产一区二区主播在线| 亚洲欧美激情一区二区| 精品国产乱码久久久久久108| 久久黄色精品视频| 日韩不卡一区| 欧美大片在线观看| 亚洲熟妇av一区二区三区 | 欧美日韩国产中文| 日本黄大片在线观看| 黄色在线免费观看大全| 久久电影网站中文字幕| 欧美激情国产高清| 久久美女免费视频| 秋霞一区二区| 欧美午夜精品久久久久久久| 正在播放精油久久| 涩爱av在线播放一区二区| 免费xxxx性欧美18vr| 久久久久久成人| 自拍偷拍视频亚洲| 成人午夜三级| 欧美日韩免费一区二区三区| 免费看黄在线看| 福利成人在线观看| 成人免费视频免费观看| 成人国产精品久久久久久亚洲| 精品小视频在线观看| 手机在线电影一区| 国产丝袜精品视频| 18深夜在线观看免费视频| 希岛爱理一区二区三区av高清| 一区二区三区精品视频| 亚洲国产精品一区在线观看不卡| 无码国产色欲xxxx视频| 国产精品一区不卡| 国产日韩综合一区二区性色av| 日本一区二区三区四区五区| 亚洲破处大片| 永久免费看mv网站入口亚洲| 久久精品一区二区免费播放| 91久久偷偷做嫩草影院电| 欧美午夜在线一二页| 国产精品后入内射日本在线观看| 欧美高清另类hdvideosexjaⅴ| 国产精品国产自产拍高清av | 国产乱码77777777| 亚洲尤物在线| 欧美精品video| 特级片在线观看| 色综合久久网| 一个色综合导航| 国产传媒第一页| 六月丁香久久丫| 欧美大片国产精品| 亚洲美女高潮久久久| 国产精品一区二区美女视频免费看| 欧美写真视频网站| 亚洲欧美国产日韩综合| 日本电影欧美片| 日韩欧美国产一区二区| 国产特级黄色大片| 三级中文字幕在线观看| 午夜视频一区二区三区| 免费人成在线观看视频播放| 女囚岛在线观看| 一区二区免费视频| www.亚洲成人网| 日本高清成人vr专区| 亚洲视频免费在线观看| 91麻豆天美传媒在线| av免费在线免费| 亚洲最新视频在线观看| 欧美这里只有精品| 在线中文字幕播放| 日本高清免费不卡视频| 成人午夜激情av| 国产成人免费精品| 91精品国产麻豆| 国产成人av免费观看| 日本成人一区二区| 欧洲视频一区二区| 免费激情视频在线观看| 亚洲天堂电影| 欧美视频自拍偷拍| 欧美性猛交xx| 精品三级av在线导航| 亚洲美女精品久久| 成人无码精品1区2区3区免费看| 91麻豆精品国产91久久久平台| 久久久精品国产| 精品肉丝脚一区二区三区| 亚洲深夜影院| 国产精品麻豆va在线播放| 99久久精品无免国产免费 | 亚洲人被黑人高潮完整版| 久久亚洲a v| 超碰一区二区| 777午夜精品视频在线播放| 性活交片大全免费看| 亚洲国产欧美日韩在线观看第一区 | 91久久国产综合久久蜜月精品| 丰满少妇在线观看bd| 久久久不卡影院| 国产欧美自拍视频| 性欧美又大又长又硬| 欧美精品粉嫩高潮一区二区| 在线黄色免费网站| 四季av在线一区二区三区| 久久免费少妇高潮久久精品99| 久久久蜜桃一区二区| 国产成人精品三级麻豆| 欧美亚洲丝袜| 日韩专区av| 欧美丝袜自拍制服另类| 中文字幕免费在线播放| 国产精品x453.com| 日韩av电影免费观看高清| www.蜜臀av| 中文欧美字幕免费| www.爱色av.com| 蜜桃在线一区| 在线视频一区二区| 少妇一级淫片免费放中国 | 69av视频在线播放| 99视频免费看| 国产视频不卡一区| 日韩a∨精品日韩在线观看| 95精品视频| 亚洲欧美中文另类| 国产成人精品亚洲男人的天堂| 久久成人免费网| 欧美日韩国产精品一区二区| 久草在线视频网站| 欧美高清www午色夜在线视频| 91久久免费视频| 亚洲精品乱码| 99在线看视频| 成人影院在线观看| 欧美日韩国产综合一区二区三区| 波多野在线播放| 99伊人成综合| 国产精品毛片va一区二区三区| 国产欧美久久久久久久久| 欧美特级限制片免费在线观看| 蜜臀av一区二区三区有限公司| 亚洲美女少妇无套啪啪呻吟| 99热99热| 亚洲羞羞网站| 91精品国产91综合久久蜜臀| 日韩免费av一区| 美女视频黄频大全不卡视频在线播放| 欧美日韩另类综合| 精品91久久| 亚洲精品天天看| 少妇一级淫片免费放中国 | 日本wwww视频| 人人网欧美视频| 2019国产精品自在线拍国产不卡| 欧美一区二区三区激情| 亚洲综合一区在线| 国产艳妇疯狂做爰视频| 极品中文字幕一区| 国产一区二区在线观看免费播放| av在线网页| 亚洲精品动漫久久久久| 国产女同在线观看| 91美女视频网站| 成人在线观看黄| 精品国产一区二区三区噜噜噜| 国产精品久久久久久久久男| 国产69精品久久app免费版| 久久精品卡一| 国产经典一区二区三区 | 96av在线| 精品剧情在线观看| 日产精品久久久久久久| 91片黄在线观看| 国产欧美高清在线| 日韩精品二区| 91久久在线视频| 天堂av中文在线| 亚洲国产精品嫩草影院久久| 四虎精品永久在线| 中文字幕av免费专区久久| 天天影视色综合| 欧美日韩在线大尺度| 久久99欧美| 免费在线观看一区| 欧美成人中文字幕| 亚洲欧洲国产综合| 欧美亚洲一区三区| 欧美日韩精品在线观看视频| 成av人片一区二区| 黄色三级视频片| 中文在线播放一区二区| 好看的日韩精品| 伊人久久大香线蕉综合四虎小说 | 91久久综合亚洲鲁鲁五月天| 中文字幕乱码一区二区免费| 亚洲黄色av网址| 亚洲国产精品久久久天堂| 成人综合色站| 国精产品一区一区三区四川| 久久国产精品久久久| 飘雪影院手机免费高清版在线观看 | 天天综合天天综合| 欧美日韩国产在线观看| 日本免费观看视| 亚洲欧洲成人精品av97| 国产精品边吃奶边做爽| 国产乱码精品一区二区三区av | 国产精品污网站| 久久精品无码一区二区三区毛片 | 国产·精品毛片| 成年人免费在线播放| 久久免费av| 精品卡一卡二| 精品国产欧美| 国产成人高清激情视频在线观看| 在线视频国产区| 一个人看的www久久| 天天操天天干天天操| 6080午夜不卡| 无码人妻久久一区二区三区不卡| 亚洲综合自拍偷拍| 欧美性x x x| 国产三级精品在线| 熟女人妻在线视频| 国产激情视频一区二区三区欧美 | 日韩av午夜在线观看| 国产婷婷一区二区三区| 中文字幕一区二区三区乱码图片| 奇米影视首页 狠狠色丁香婷婷久久综合 | 国产亚洲欧美在线视频| 欧美日韩免费| 一级黄色免费在线观看| 欧洲杯足球赛直播| 欧美裸体网站| 天海翼精品一区二区三区| 粉嫩av一区二区三区免费观看| 男人亚洲天堂| 国产精品福利在线观看网址| 亚洲精品一区| 2020欧美日韩在线视频| 韩日毛片在线观看| 久久久视频在线| 婷婷色在线播放| 久久久国产一区| 在线看免费av| 日韩亚洲欧美中文高清在线| 91精品专区| 在线激情影院一区| 激情福利在线| 亚洲色图综合久久| 国产视频精选在线| 亚洲偷熟乱区亚洲香蕉av| 奇米影视888狠狠狠777不卡| 亚洲美女黄色片| 国产有码在线| 国产一区二区三区免费视频| 国产人成在线视频| 丝袜亚洲欧美日韩综合| 黄在线免费看| 美女性感视频久久久| av网站网址在线观看| 美女视频久久黄| 免费在线国产视频| 97欧美精品一区二区三区| 成人性教育av免费网址| 国产精品v片在线观看不卡| 日韩在线观看不卡| 国产日韩欧美自拍| 精品国产乱码久久久久久樱花| 97视频资源在线观看| 国产精品巨作av| 精品无人区一区二区三区| 美女久久久久| 一区二区三区四区| 国产精品激情| 国产日产欧美视频| 蜜臀国产一区二区三区在线播放 | sm在线播放| 日本成人激情视频| 四虎在线精品| 国产精品永久入口久久久| 亚洲国产欧美日韩在线观看第一区| 日韩精品资源| 亚洲视频在线免费| 777777av| 麻豆视频观看网址久久| 不卡的一区二区| 337p粉嫩大胆噜噜噜噜噜91av | 国产精品色哟哟网站| 草视频在线观看| 欧美日韩在线视频观看| 最近中文字幕在线观看| 日韩欧美亚洲另类制服综合在线| 亚州精品国产精品乱码不99按摩| 在线成人免费网站| 激情影院在线| 国产精品高潮呻吟久久av黑人| 免费一级欧美在线大片| 欧美色欧美亚洲另类七区| 一区二区三区四区在线观看国产日韩| 青青草成人免费在线视频| 麻豆成人免费电影| 波多野结衣影院| 国产精品高潮呻吟久久| 91看片在线播放| 欧美军同video69gay| 偷拍自拍在线| 久久最新资源网| 婷婷综合六月| 国产精品免费区二区三区观看| 色婷婷热久久| 人妻熟女一二三区夜夜爱| 国产乱码精品一区二区三区五月婷| 成人免费无遮挡无码黄漫视频| 一区二区三区四区蜜桃 | 欧美三级视频在线| 无码精品在线观看| 久久久精品久久| 日本综合字幕| 国产亚洲自拍偷拍| 在线电影一区二区| 国产一二三区av| 99久久精品国产一区| 日本中文字幕免费在线观看| 欧美在线视频全部完| 亚洲欧美日韩动漫| 欧美情侣性视频| 成人51免费| 一区二区精品在线观看| 日韩成人午夜电影| 亚洲天堂资源在线| 亚洲线精品一区二区三区| 国产乱码精品一区二区| 亚洲另类春色校园小说| 一区二区三区免费看| 性欧美xxxx大乳国产app| 天天躁日日躁狠狠躁av| 亚洲精品久久7777| jlzzjlzz亚洲女人18| 久久九九国产精品怡红院| 九九久久国产| 天堂av一区二区| 日韩经典中文字幕一区| 色欲av无码一区二区三区| 精品久久久久久久久中文字幕| 涩涩视频免费看| 97久久伊人激情网| 小说区图片区色综合区| 美女福利视频在线| 久久久久综合网| chinese国产精品| 亚洲欧美成人网| 成人影院入口| 日本在线成人一区二区| 日本在线播放一区二区三区| 亚洲精品成人无码| 欧美视频一区在线观看| 五月天婷婷在线视频| 成人精品一区二区三区电影免费| 五月婷婷亚洲| 一区二区三区人妻| 亚洲一区二区免费视频| 内射后入在线观看一区| 97视频在线免费观看| 色婷婷av一区二区三区丝袜美腿| 欧美性久久久久| 中文字幕第一区| 国产精品无码久久久久成人app| 久久艹在线视频| av不卡一区二区| 国产原创中文在线观看| 久久你懂得1024| 最近中文字幕免费观看| 欧美成人精品在线播放| 国产三级精品三级在线观看国产| 大陆极品少妇内射aaaaa| 国产午夜久久久久| 国产伦精品一区二区三区免.费 | 黄色免费在线观看网站| 97视频中文字幕| 香蕉成人久久| 国产精品18在线| 精品少妇一区二区三区| 日产福利视频在线观看| 亚洲一区二区三区免费看| 国产.欧美.日韩| 亚洲欧美另类在线视频| 欧美成人三级视频网站| 日韩精品a在线观看91|