你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 如何做優化,UITabelView才能更加順滑

如何做優化,UITabelView才能更加順滑

編輯:IOS開發基礎

105.jpg

原文由Alexander Orlov發表於medium,地址為https://medium.com/ios-os-x-development/perfect-smooth-scrolling-in-uitableviews-fd609d5275a5#.so9tpnlk1

這篇文章是前兩周@葉孤城葉大在微信群裡面的分享,一直到這兩天才翻出來研究。很多實用的東西,不過由於水平有限,有些地方沒能翻譯好,還請大家指正。

我已經在iOS這個最好的移動平台上有幾年的開發經驗了。在這期間,我已以接觸過很多的iOS應用和iOS工程師。

我們的世界很多好的開發者,但有時我發現他們中的一些人並不是很清楚如何充分利用這個最受歡迎的移動設備的整體潛力,來開發真正平滑的應用。

現在,我將嘗試從我的視角,來說明一下為了讓UITableView更快更平滑,工程師應該做哪些優化。

文章越往後,難度和深度也會不斷增加,所以我將以一些你熟悉的東西來開始。文章後面將會討論iOS繪畫系統和UIKit更深層次的一些東西。


內建方法

我相信大多數閱讀這篇文章的人都知道這些方法,但一些人,即便是使用過這些方法,也沒有以正確的姿式來使用它們。

******

首先是重用cell/header/footer的單個實例,即便是我們需要顯示多個。這是優化UIScrollView(UITableView的父類)最明顯的方式,UIScrollView是由蘋果的工程師提供的。為了正確的使用它,你應該只有cell/header/footer類,一次性初始化它們,並返回給UITableView。

在蘋果的開發文檔裡面已經描述了重用cell的流程,在這就沒有必須再重復了。

但重要的事情是:在UITableView的dataSource中實現的tableView:cellForRowAtIndexPath:方法,需要為每個cell調用一次,它應該快速執行。所以你需要盡可能快地返回重用cell實例。

不要在這裡去執行數據綁定,因為目前在屏幕上還沒有cell。為了執行數據綁定,可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:中進行。這個方法在顯示cell之前會被調用。

******

第二點也不難理解,但是有一件事需要解釋一下。

這個方法對於cell定高的UITableView來說沒有意義,但如果由於某些原因需要動態高度的cell的話,這個方法可以很容易地讓滑動更流暢。

正如我們所知,UITableView是UIScrollView的子類,而UIScrollView的作用是讓用戶可以與比屏幕實際尺寸更大的區域交互。任何UIScrollView的實例都使用諸如contentSize、contentOffset和其它許多屬性來將正確的區域顯示給用戶。

但是UITableView的問題在哪?正如所解釋的一樣,UITableView不會同時維護所有cell的實例。相反,它只需要維護顯示給用戶的那些cell。

那麼,UITableView是如何知道它的contentSize呢?它是通過計算所以cell的高度之和來計算contentSize的值。

UITableView的delegate方法tableView:heightForRowAtIndexPath:會為每個cell調用一次,所以你應該非常快地返回高度值。

很多人會犯一個錯誤,他們會在布局初始化cell實例並綁定數據後去獲取它們的高度。如果你想優化滑動的性能,就不應該以這種方式來計算cell的高度,因為這事難以置信的低效,iOS設備標准的60 FPS將會降低到15-20 FPS,滑動會變得很慢。

如果我們沒有一個cell的實例,那如何去計算它的高度呢?這裡有一段示例代碼,它使用類方法,並基於傳入的寬度及顯示的數據來計算高度值:

1-l_1bUrp8OzZcS5cHdnaGvw (1).png

可以用以下方式來使用上面這個方法返回高度值給UITableView:

1-keqsdc6XFmEOdmI4w10zrA.png

你在實現這一切的時候能獲得了多少樂趣呢?大多數人會說沒有。我沒有保證過這事很容易。當然,我們可以構建我們自己的類來手動布局和計算高度,但有時候我們沒有足夠的時間來做這件事。你可以在Telegram的iOS應用代碼中找到這種實現的例子。

從iOS 8開始,我們可以在UITableView的delegate中使用自動高度計算,而不需要實現上面提到的方法。為了實現這一功能,你可能會使用AutoLayout,並將rowHeight變量設置為UITableViewAutomaticDimension。可以在StackOverflow中找到更多詳細的信息。

盡管可以使用這些方法,但我強烈建議不要使用它們。另外,我也不建議使用復雜的數學計算來獲取cell的高度,如果可能,只使用加、減、乘、除就可以。

但如果是AutoLayout呢?它真的跟我所說的一樣慢麼?你可能會很驚訝,但這是事實。如果你想讓你的App在所有設備上都能平滑的滾動,你就會發現這種方法難以置信的慢。你使用的子視圖越多,AutoLayout的效率越低。

AutoLayout相對低效的原因是隱藏在底層的命名為”Cassowary“的約束求解系統。如果布局中子視圖越多,那麼需要求解的約束也越多,進而返回cell給UITableView所花的時間也越多。

哪一個更快呢:使用少量的值來執行基本的數學計算,還是找一個求解大量線性等式或不等式的系統麼?現在想像一下,用戶想要快速地滑動,每個cell的自動布局也執行著瘋狂的計算。

******

使用內建方法優化UITableView的正確方法是:

  • 重用cell實例:對於特殊類型的cell,你應該只有一個實例,而沒有更多。

  • 不要在cellForRowAtIndexPath:方法中綁定數據,因為在此時cell還沒有顯示。可以使用UITableView的delegate中的tableView:willDisplayCell:forRowAtIndexPath:方法。

快速計算cell高度。對於工程師來說這是常規工作,但你將會為優化復雜cell的平滑滑動所付出的耐心而獲取回報。


我們需要更深一步

當然,上面提到的這些點不足以實現真正的平滑滾動,特別是當你需要實現一些復雜的cell(如有大量的漸變、視圖、交互元素、一些修飾元素等等)時,這變得尤其明顯。

這種情況下,UITableView很容易變得緩慢,即便是做了上面所有的事情。UITableViewCell中的視圖越多,滑動時FPS越低。但在使用了手動布局和優化了高度計算後,問題就不在布局了,而在渲染了。

******

讓我們把關注點放在UIView的opaque屬性上。文檔中說它用於輔助繪圖系統定義UIView是否透明。如果不透明,則繪圖系統在渲染視圖時可以做一些優化,以提高性能。

我們需要性能,或者不是?用戶可能快速地滑動table,如使用scrollsToTop特性,但他們可能沒有最新的iPhone,所以cell必須快速地被渲染。比通常的視圖更快。

渲染最慢的操作之一是混合(blending)。混合操作由GPU來執行,因為這個硬件就是用來做混合操作的(當然不只是混合)。

你可能已經猜到,提高性能的方法是減少混合操作的次數。但在此之前,我們需要找到它。讓我們來試試。

在iOS模擬器上運行App,在模擬器的菜單中選擇’Debug‘,然後選中’Color Blended Layers‘。然後iOS模擬器就會將全部區域顯示為兩種顏色:綠色和紅色。

綠色區域沒有混合,但紅色區域表示有混合操作。

001.png002.png

正如你所看到的一樣,在cell中至少有兩處執行了混合操作,但你可能看不出差別來(這個混合操作是不必要的)。

每種情況都應該仔細研究,不同的情況需要使用不同的方法來避免混合。在我這裡,我需要做的只是設置backgroundColor來實現非透明。

但有時候可能更復雜。看看這個:我們有一個漸變,但是沒有混合。

003.png004.png

如果想要使用CAGradientLayer來實現這個效果,你將會很失望:在iPhone 6中FPS將會降到25-30,快速滑動變得不可能。

這確實發生了,因為我們混合了兩個不同層的內容:UILabel的CATextLayer和我們的CAGradientLayer。

如果能正確地利用了CPU和GPU資源,它們將會均勻地負載,FPS保持在60幀。看起來就像下面這樣:

005.png

當設備需要執行很多混合操作時,問題就出現了:GPU是滿載的,但CPU卻保持低負載,而顯得沒有太大用處。

大多數工程師在2010年夏季末時都面臨這個問題,當時發布了iPhone 4。Apple發布了革命性的Retina顯示屏和…非常普通的GPU。然而,通常情況下它仍然有足夠的能力,但上面描述的問題卻變得越來越頻繁。

你可以在當前運行iOS 7系統的iPhone 4上看到這一現象—所有的應用都變得很慢,即使是最簡單的應用。不過,應用這篇文章中的介紹的方法,即使是在這種情況下,你的應用也能達到60 FPS,盡管會有些困難。

所以,需要怎麼做呢?事實上,解決方案是:使用CPU來渲染!這將不會加載GPU,這樣就無法執行混合操作。例如,在執行動畫的CALayer上。

我們可以在UIView的drawRect:方法中使用CoreGraphics操作來執行CPU渲染,如下所示:

006.png

這段代碼nice麼?我會告訴你並非如此。甚至通過這種方式,你會撤銷在一些UIView上(在任何情況下,它們都是不必要的)的所有緩存優化操作。但是,這種方法禁用了一些混合操作,卸載GPU,從而使UITableView的更順暢。

但是記住:這提高了渲染性能,不是因為CPU比GPU更快!它可以讓我們通過為讓CPU來執行某些渲染任務,從而卸載GPU,因為在很多情況下,CPU可能不是100%負載的。

優化混合操作的關鍵點是在平衡CPU和GPU的負載。

******

優化UITableView中繪制數據操作的小結:

  • 減少iOS執行無用混合的區域:不要使用透明背景,使用iOS模擬器或者Instruments來確認這一點;如果可以,盡量使用沒有混合的漸變。

  • 優化代碼,以平衡CPU和GPU的負載。你需要清楚地知道哪部分渲染需要使用GPU,哪部分可以使用CPU,以此保持平衡。

  • 為特殊的cell類型編寫特殊的代碼。


像素獲取

你知道像素看起來是什麼樣的麼?我的意思是,屏幕上的物理像素是什麼樣的?我肯定你知道,但我還是想讓你看一下:

1-P3g0HtD5LFzWJXQjsNDbUg.jpeg

不同的屏幕有不同的制作工藝,但有一件事是一樣的。事實上,每個物理像素由三個顏色的子像素組成:紅、綠、藍。

基於這一事實,像素不是原子單位,雖然對於應用來說它是。或者仍然不是?

直到帶有Retina屏的iPhone 4發布前,物理像素都可以用整型點坐標來描述。自從有了Retina屏後,在Cocoa Touch環境下,我們就可以用屏幕點來取代像素了,同時屏幕點可以是浮點值。

在完美的世界中(我們嘗試構建的),屏幕點總是被處理成物理像素的整型坐標。但在現實生活中它可能是浮點值,例如,線段可能起始於x為0.25的地方。這時候,iOS將執行子像素渲染。

這一技術在應用於特定類型的內容(如文本)時很有意義。但當我們繪制平滑直線時則沒有必要。

如果所有的平滑線段都使用子像素渲染技術來渲染,那你會讓iOS執行一些不必要的任務,從而降低FPS。

******

什麼情況下會出現這種不必要的子像素抗鋸齒操作呢?最常發生的情況是通過代碼計算而變成浮點值的視圖坐標,或者是一些不正確的圖片資源,這些圖片的大小不是對齊到屏幕的物理像素上的(例如,你有一張在Retina顯示屏上的大小為60*61的圖片,而不是60*60的)。

在前面我們講到,要解決問題,首先需要找到問題在哪。在iOS模擬器上運行程序,在”Debug“菜單中選中”Color Misaligned Image“。

這一次有兩種高亮區域:品紅色區域會執行子像素渲染,而黃色區域是圖片大小沒有對齊的情況。

007.png008.png

那如何在代碼中找到對應的位置呢?我總是使用手動布局,並且部分會自定義繪制,所以通常找到這些地方沒有任何問題。如果你使用Interface Builder,那我對此深表同情。

通常,為了解決這個問題,你只要簡單地使用ceilf, floorf和CGRectIntegral方法來對坐標做四捨五入處理。就是這樣!

******

通過上面的討論,我想建議你以下幾點:

  • 對所有像素相關的數據做四捨五入處理,包括點坐標,UIView的高度和寬度。

  • 跟蹤你的圖像資源:圖片必須是像素完美的,否則在Retina屏幕上渲染時,它會做不必要的抗鋸齒處理。

  • 定期復查你的代碼,因為這種情況可以會經常出現。


異步UI

可能這看起來有點奇怪,但這是一種非常有效的方法。如果你知道如何做,那麼可以讓UITableView滑動得更平滑。

現在我們來討論一下你應該做什麼,然後再討論下你是否可能這麼做。

******

每個中等以上規模的應用都可能會使用帶有媒體內容的cell:文本、圖片、動畫,甚至還有視頻。

而所有這些都可能帶有裝飾元素:圓角頭像、還’#‘號的文本、用戶名等。

我們已經多次提及盡可能快地返回cell的需求,而在這裡有一些麻煩:clipsToBounds很慢,圖片需要從網絡加載,需要在字符串中定位#號,和許多其它的問題。

優化的目標是很明確的:如果在主線程中執行這些操作,則會讓你不能很快地返回cell。

在後台加載圖片,在相同的地方處理圓角,然後將處理後的圖片指定給UIImageView。

立刻顯示文本,但在後台定位#號,然後使用屬性字符串來刷新顯示。

在你的cell中,需要具體情況具體分析,但主要的思想是在後台執行大的操作。這可能不止是網絡代碼,你需要使用Instruments來找到它們。

記住:需要盡快返回cell。

******

有時候,上面的所有技術可能都幫不上忙。如GPU仍然不能使用(iPhone4+iOS7)時,cell中有很多內容時,需要CALayer的支持以實現動畫時(因為在drawRect:中實現起來真的很麻煩)。

在這種情況下,我們需要在後台渲染所有其它東西。此外它能在用戶快速滑動UITableView時有效地提高FPS。

我們來看看Facebook的應用。為了檢測這些,你可能需要往下滑足夠的高度,然後點擊狀態欄。列表會往上滑動,因此你可以清楚地看到此時沒有渲染cell。如果想要更精確,則不能及時獲得。

這很簡單,所以你可以自己試試。這時,你需要設置CALayer的drawsAsynchronously屬性為YES。

但是我們可以檢查這些行為的必要性。在iOS模擬器上運行程序,然後選擇“Debug”菜單中的”Color Offscreen-Rendered“。現在所有在後台渲染的區域都被高亮為黃色。

009.png010.png

如果你為某些層開啟了這一模式,但是它沒有高亮顯示,那麼它就不夠慢。

為了在CALyaer層找到瓶頸並進一步減少它,你可以使用Instruments裡面的Time Profiler。

******

這裡是異步化UI的實現清單:

  • 找到讓你的cell無法快速返回的瓶頸。

  • 將操作移到後台線程,並在主線程刷新顯示的內容。

  • 最後一招是設置你的CALayer為異步顯示模式(即使只是簡單的文本或圖片)—這將幫你提高FPS。


結論

我嘗試解釋了iOS繪圖系統(沒有使用OpenGL,因為它的情況更少)的主要思路。當然有些看起來很模糊,但事實上這只是一些方向,你應該朝著這些方向來檢查你的代碼以找出影響滾動性能的所有問題。

具體情況具體分析,但原則是不變的。

獲取完美平滑滾動的關鍵是非常特殊的代碼,它能讓你竭盡iOS的能力來讓你的應用更加平滑。

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