你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> ibireme 之怎樣讓iOS 堅持界面流利(原理)

ibireme 之怎樣讓iOS 堅持界面流利(原理)

編輯:IOS開發綜合

如何讓IOS 堅持界面流利?這些技巧你知道嗎

作者:ibireme
這篇文章會十分詳細的剖析 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 中的數據,方便停止比照。雖然官方使用面前的功用更多更為復雜,但不至於會帶來太大的交互功能差別。

這個 Demo 最低可以運轉在 IOS 6 上,所以你可以把它跑到老設備上體驗一下。在我的測試中,即便在 iPhone 4S 或許 iPad 3 上,Demo 列表在疾速滑動時依然能堅持 50~60 FPS 的流利交互,而其他諸如微博、冤家圈等使用的列表視圖在滑動時曾經有很嚴重的卡頓了。

微博的 Demo 有大約四千行代碼,Twitter 的只要兩千行左右代碼,第三方庫只用到了 YYKit,文件數量比擬少,方便檢查。好了,上面是注釋。

屏幕顯示圖像的原理

首先從過來的 CRT 顯示器原理說起。CRT 的電子槍依照下面方式,從上到下一行行掃描,掃描完成後顯示器就出現一幀畫面,隨後電子槍回到初始地位持續下一次掃描。為了把顯示器的顯示進程和零碎的視頻控制器停止同步,顯示器(或許其他硬件)會用硬件時鐘發生一系列的定時信號。當電子槍換到新的一行,預備停止掃描時,顯示器會收回一個程度同步信號(horizonal synchronization),簡稱 HSync;而當一幀畫面繪制完成後,電子槍回復到原位,預備畫下一幀前,顯示器會收回一個垂直同步信號(vertical synchronization),簡稱 VSync。顯示器通常以固定頻率停止刷新,這個刷新率就是 VSync 信號發生的頻率。雖然如今的設備大都是液晶顯示屏了,但原理依然沒有變。

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

在最復雜的狀況下,幀緩沖區只要一個,這時幀緩沖區的讀取和刷新都都會有比擬大的效率問題。為理解決效率問題,顯示零碎通常會引入兩個緩沖區,即雙緩沖機制。在這種狀況下,GPU 會事後渲染好一幀放入一個緩沖區內,讓視頻控制器讀取,當下一幀渲染好後,GPU 會直接把視頻控制器的指針指向第二個緩沖器。如此一來效率會有很大的提升。

雙緩沖雖然能處理效率問題,但會引入一個新的問題。當視頻控制器還未讀取完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩沖區並把兩個緩沖區停止交流後,視頻控制器就會把新的一幀數據的下半段顯示到屏幕上,形成畫面撕裂景象,如下圖:

為理解決這個問題,GPU 通常有一個機制叫做垂直同步(簡寫也是 V-Sync),當開啟垂直同步後,GPU 會等候顯示器的 VSync 信號收回後,才停止新的一幀渲染弛緩沖區更新。這樣能處理畫面撕裂景象,也添加了畫面流利度,但需求消費更多的計算資源,也會帶來局部延遲。

那麼目前主流的挪動設備是什麼狀況呢?從網上查到的材料可以知道,iOS 設備會一直運用雙緩存,並開啟垂直同步。而安卓設備直到 4.1 版本,Google 才開端引入這種機制,目前安卓零碎是三緩存+垂直同步。

卡頓發生的緣由和處理方案

在 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

2

3

4

5

NSArray*tmp=self.array;

self.array=nil;

dispatch_async(queue,^{

[tmpclass];

});

規劃計算

視圖規劃的計算是 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

2

3

4

5

6

7

8

9

10

11

-(void)display{

dispatch_async(backgroundQueue,^{

CGContextRefctx=CGBitmapContextCreate(...);

//draWincontext...

CGImageRefimg=CGBitmapContextCreateImage(ctx);

CFRelease(ctx);

dispatch_async(mainQueue,^{

layer.contents=img;

});

});

}

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 的由來

ASDK 的作者是 Scott Goodson (Linkedin),他已經在蘋果任務,擔任 iOS 的一些內置使用的開發,比方股票、計算器、地圖、鐘表、設置以及Safari 等,當然他也參與了 UIKit framework 的開發。後來他參加 Facebook 後,擔任 Paper 的開發,創立並開源了 AsyncDisplayKit。目前他在 Pinterest 和 Instagram 擔任 iOS 開發和用戶體驗的提升等任務。

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 以為,阻塞主線程的義務,次要分為下面這三大類。文本和規劃的計算、渲染、解碼、繪制都可以經過各種方式異步執行,但 UIKit 和 Core Animation 相關操作必需在主線程停止。ASDK 的目的,就是盡量把這些義務從主線程挪走,而挪不走的,就盡量優化功能。
為了達成這一目的,ASDK 嘗試對 UIKit 組件停止封裝:

這是罕見的 UIView 和 CALayer 的關系:View 持有 Layer 用於顯示,View 中大局部顯示屬性實踐是從 Layer 映射而來;Layer 的 delegate 在這裡是 View,當其屬性改動、動畫發生時,View 可以失掉告訴。UIView 和 CALayer 不是線程平安的,並且只能在主線程創立、訪問和銷毀。

ASDK 為此創立了 ASDisplayNode 類,包裝了罕見的視圖屬性(比方 frame/bounds/alpha/transform/backgroundColor/superNode/subNodes 等),然後它用 UIView->CALayer 相反的方式,完成了 ASNode->UIView 這樣一個關系。

當不需求呼應觸摸事情時,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 的圖層預分解

有時一個 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 異步並發操作

自 iPhone 4S 起,iDevice 曾經都是雙核 CPU 了,如今的 iPad 甚至曾經更新到 3 核了。充沛應用多核的優勢、並發執行義務對堅持界面流利有很大作用。ASDK 把規劃計算、文本排版、圖片/文本/圖形渲染等操作都封裝成較小的義務,並應用 GCD 異步並發執行。假如開發者運用了 ASNode 相關的控件,那麼這些並發操作會自動在後台停止,無需停止過多配置。

Runloop 義務分發

Runloop work distribution 是 ASDK 比擬中心的一個技術,ASDK 的引見視頻和文檔中都沒有詳細展開引見,所以這裡我會多做一些剖析。假如你對 Runloop 還不太理解,可以看一下我之前的文章深化了解RunLoop,外面對 ASDK 也有所提及。

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 來執行少量繪制義務時,偶然會遇到這種問題:

少量的義務提交到後台隊列時,某些義務會由於某些緣由(此處是 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 耗費等,在定位界面卡頓的問題時,這是最好的工具。

南來的,北往的,下班的,下崗的,走過路過不要錯過!

======================特性簽名=====================

之前以為Apple 的iOS 設計的要比 Android 波動,我錯了嗎?

下載的許多客戶端順序/游戲順序,常常會Crash,是順序寫的不好(內存走漏?剛啟動也會嗎?)還是iOS自身的不波動!!!

假如在Android手機中可以復雜聯接到ddms,就可以檢查零碎log,很容易看到順序為什麼出錯,在iPhone中如何得知呢?試試Organizer吧,剖析一下Device logs,也許有用.

ibireme 之怎樣讓iOS 保持界面流暢(原理)【ibireme 之怎樣讓iOS 堅持界面流利(原理)】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved