ios 性能优化(一)
-
邏輯優化
- 代碼封裝優化
- 代碼執行效率優化
-
界面優化
- 離屏渲染優化
- 界面加載優化
邏輯優化
代碼封裝優化
代碼的封裝優化主要是細化代碼的功能,每個功能單獨提取出來做成一個方法,當其他地方需要用到同樣功能時直接調用該方法即可,無需寫重復代碼,減少代碼量,增加代碼的重用性,方便單元測試。
例如:一個過濾輸入文本內容的方法,需要過濾特殊字符和表情
上面的方法雖然實現了需要的功能,但是卻顯不靈活。假如我只想過濾表情,只想過濾特殊字符或者想過濾其他的內容,則需要重新寫一個方法來滿足功能。但是功能內部的代碼卻大致相同。這樣就增加了代碼量且使得代碼看起來非常臃腫。
對上面的代碼進行封裝優化的方案有很多種,見仁見智,主要在思路而不在方法。
例如:我選擇把過濾表情單獨的提取出來,成一個根據正則表達式來過濾內容的方法,而過濾特殊字符串提取出來,成一個根據傳入的字符來過濾內容的方法。
這樣是方法中的功能性單一,但針對性卻不單一。大大的提高了代碼的重用性和單元測試。
代碼的封裝很重要,體現程序員的編程思維的遠見性,代碼的可擴展性。在合作開發時,能方便他人
代碼執行效率優化
執行效率的優化主要在于得到結果的快慢,例如你想要一樣東西,某寶和某東都有且價格差不多,但某寶要兩天才能拿到,而某東當天下午就可以拿到。當然大家都會某東啦...這就是效率的優勢。
關于代碼的執行效率其實還有很多地方,礙于本人目前的眼界和水平有限,后期會驗證后添加更多
- 效率1:我們最常用的for循環
執行的結果:
for循環用時:0.018385 forIn循環用時:0.017044 enumerateKeysAndObjectsUsingBlock用時:0.016417看上去是enumerateKeysAndObjectsUsingBlock更快,但是執行多次后 ,你會發現,有時for快有時forin快有時enumerateKeysAndObjectsUsingBlock快。那是因為數據量比較少。
假如把上面代碼里有100000個數據。
結果:
for循環明顯更快,不論嘗試多少次結果都是for循環明顯快。之所以用時20多秒是因為循環內部打印了日志,因為打印日志是非常耗時的操作。當然可以不用在內部去打印日志。結果依然是for循環更快。而往往在開發中,for循環內部執行的操作都是比較多并且耗時的。
所以在數據量小時for,forIn,enumerateKeysAndObjectsUsingBlock都可以。在大量數據時,盡量用for循環去執行。經過測試,執行效率上NSDictionary < NSArray < NSSet 。NSSet的執行效率最高
由此可見很多時候在獲取特定的數據時算法的選擇會即決定了代碼執行次數也決定了執行效率。作為一個開發者要了解最基本的各類算法
冒泡排序、快速排序、插入排序、歸并排序、希爾排序、動態排序。這些都是提供執行效率的基本算法。必須掌握了解的,這里就不詳說了。不懂的朋友度娘
離屏渲染
說到離屏渲染,需要先了解屏幕每一幀界面是如何得到的。
- 需要了解的知識點:
屏幕顯示原理
首先從過去的CRT顯示器原來說起,CRT的電子槍是按照上圖的方式,從上到下一行一行掃描并曾現每幀畫面的。為了把顯示器的顯示過程和系統的視頻控制器進行同步,顯示器(或其他硬件)會用硬件時鐘產生一系列的信號,當電子槍換到新的一行,準備進行掃描時,顯示器會發送一個水平同步信號(horizonal synchronization 簡稱HSync),而當一幀畫面繪制完成后,電子槍回復到原位準備下一幀時,會發送一個垂直同步信號(vertical synchronization 簡稱VSync)。顯示器通常以固定頻率刷新,而這個頻率就是VSync信號產生的頻率。盡管現在的設備都是用液晶顯示屏,但是原理仍然沒有改變。
目前IOS設備采用的是雙緩存+垂直同步,而Android在4.1后采用的是三緩存+垂直同步。
雙緩存機制:GPU會預先渲染好一幀放入下一個緩存區內,讓視頻控制器取出,當下一幀渲染好后,GPU會直接把視頻控制器的指針指向第二個緩沖器,如此來提高效率。即屏幕顯示一幀,GPU預備下一幀。而不是顯示完一幀在計算下一幀。
?
每一幀的由來,當收到VSync信號后,首先,系統圖形服務會通過CADisplayLink等機制通知App,然后,App主線程開始在CPU中計算顯示內容,比如視圖等創建、布局、圖片解碼、文本繪制等,接著,CPU計算好的內容提交到GPU區,由GPU進行變換、合成、渲染。隨后GPU將渲染結果提交到幀緩沖區,等到下一次收到VSync信號時顯示上一幀計算好的內容。
界面卡頓,則是所謂的幀丟失,當在一個VSync信號內(每秒60幀,每一幀1/60秒),CPU或者GPU沒有計算好內容,系統就會丟棄這一幀的內容,而屏幕依然顯示的是之前的內容,就造成了界面卡頓。
GPU屏幕渲染有以下兩種方式:
?
- on-screen Rendering:在當前屏幕渲染,指的是GPU的渲染操作實在當前用于顯示的屏幕緩沖區進行的;
- off-screen Rendering:在當前屏幕以外的區域渲染,既離屏渲染,指的是在當前顯示的屏幕緩沖區以外的區域開辟出來的一個新的緩沖區去進行渲染操作。
由上面知識點,可以看出離屏渲染是在GPU中造成的。
- 離屏渲染:所謂的離屏渲染即是在GPU計算時,由于界面層次復雜混合度大等造成計算的復雜度過大,導致GPU需要重新創建一個額外的屏幕外緩沖區計算這個位圖。當計算好后在轉換到幀緩沖區。這一次的渲染是脫離了屏幕而在屏幕以外的區域渲染完成的,所以叫做離屏渲染。
創建額外的屏幕外緩沖區去計算位圖,再去替換屏幕內容的代價是非常大且耗時的。解決離屏渲染是提升用戶體驗非常重要的點,因為離屏渲染會導致幀丟失界面卡頓資源消耗。
補充知識點:
UIView和CALayer的關系
The Relationship Between Layers and Views這里有一篇關于它倆關系的詳細說明
簡單的說UIView是基于CALayer進行封裝的。UIView的每個屬性都對應這CALayer的一個屬性。
?
而CALayer負責顯示UIView的具體內容,UIView負責提供內容和處理響應事件等,也就是說我們在手機上看見的都是CALayer所呈現的內容。下面是CALayer的結構圖
CALayer結構圖CALayer由三個視覺元素組成,background背景 、contents內容、border邊框。而中間的contents的屬性聲明為var contents: AnyObject?實際上它必須是個CGImage才能顯示。
?
造成離屏渲染的點:
- shouldRasterize(光柵化)
當設置shouldRasterize = YES時,會把光柵化的圖片保存成一個bitmap緩存起來,當下一次要顯示這個圖層時,CPU會直接從緩存中拿取位圖,傳給GPU,而不需要GPU再去渲染這一部分的圖層,減少GPU的渲染計算。 可以通過Instruments core animation或者模擬器 中的 Color Hits Green and Misses Red來查看圖層是否被緩存了,綠色表示緩存,紅色表示沒有緩存。一般視圖shouldRasterize默認為NO,對于經常變換的視圖不要使用shouldRasterize。會造成性能消耗的浪費。
關于shouldRasterize是一個有取有舍的屬性,對于那些復雜但是內容不長變的視圖可以用shouldRasterize來緩存內容,減少GPU每次的計算,達到性能提高。但是要慎用,目前本人項目中還沒有使用過shouldRasterize來緩存內容。
- mask(遮罩層)
屏幕上的每一個像素點是由當前像素點上多層layer通過GPU混合顏色計算出來的,視圖的layer一般在最下層,陰影則在視圖layer之下。mask是layer的一個屬性,它也是CALayer類型的,從官方對該屬性的注釋可知,默認情況下mask是為nil不存在的。mask相當于一個遮罩層,覆蓋在視圖的layer的上層,如果視圖的layer是contentLayer,那么為這個layer添加一個mask,可以用mask來控制視圖顯示的區域和透明度。在mask的區域內的contentLayer會被顯示,而之外的將不被顯示,而區域內的contentLayer將通過mask層把像素顏色傳遞出去,如果mask的opacity不為1,那么mask將會按照opacity值過濾contentLayer的內容。當為視圖設置了mask后,mask的復雜度會決定GPU的計算復雜度,當mask的opacity不為1時或者視圖的alpha不為1,那么GPU將進行多層layer的混合顏色計算。
- shadows(陰影)
陰影是直接合成一個在視圖下面的layer,而不是在下面創建一個新的視圖來當做陰影,當陰影的透明度不為1時,它的渲染復雜度會比較大。
- EdgeAnntialiasing(抗鋸齒)
allowsEdgeAntialiasing是ios7以后提供的方法,用來抗鋸齒,有時候圖片縮放或者界面旋轉會造成邊框出現鋸齒。而鋸齒的計算是非常耗性能的會造成離屏渲染的。所以在出現鋸齒情況下allowsEdgeAntialiasing設置為YES
- GroupOpacity(不透明)
allowsGroupOpacity是設置視圖子視圖在透明度上是否跟父視圖一樣,一般默認情況下是為YES的。如果父視圖的透明度不為1,那么子視圖的透明度也不會為1。在GPU渲染的時候,就會造成既要渲染子視圖還要渲染子視圖下面的父視圖內容,然后合成視圖。這樣造成GPU計算復雜度增大需要離屏渲染解決。
- 復雜形狀比如圓角等
這里復雜形分為兩種
一種是有系統設置造成的形狀,比如設置圓角用maskToBundle加cornerRadius這種是有系統剪裁形成的圓角形狀。
另一種是繪制生成的形狀,比如圖片中有圓角區域外是透明的或者直接繪制圓角。
系統形狀會造成GPU的消耗,因為剪裁會很耗性能,而繪制會造成CPU性能消耗高,因為繪制工作是由CPU造成的
- 漸變
漸變的渲染計算是非常負責好性能的。
- Color Blended layers
標示混合的圖層會為紅色,不透明的圖層為綠色,通常我們希望綠色的區域越多越好。
Color Hits Green and Misses Red
假如我們設置viewlayer的shouldRasterize為YES,那些成功被緩存的layer會標注為綠色,反之為紅色,下面會有詳細介紹。 - Color copied images
標示那些被Core Animation拷貝的圖片。這主要是因為該圖片的色彩格式不能被GPU直接處理,需要在CPU這邊做轉換,假如在主線層做這個操作對性能會有一定的影響。 - Color misaligned images
被縮放的圖片會被標記為黃色,像素不對齊則會標注為紫色。 - Color offscreen-rendered yellow
標示哪些layer需要做離屏渲染(offscreen-render)
從上面的點相信你已經了解到了造成離屏渲染的原因。
下面是關于離屏渲染、界面優化的方法
- (1.)圓圖:
本人在經過各種測試和觀看各種文章資料后百思不得其解,從原理上來說上面指出的離屏渲染的幾個點確實會造成離屏渲染。但是我代碼測試并查看Color Off-Screen Rendered。居然沒有高亮黃色。。。納尼!!!難道是蘋果又做了優化了。。。正在查找蘋果文檔
接下來說說Color Blender Layer,在模擬器中旋轉Debug--> Color Blender Layer。模擬器界面中出現綠色的部分表示沒有透明內容,紅色的表示有透明內容。對于好的程序來說,綠色越多越好,上面離屏渲染講過了,透明會造成GPU的計算復雜度變大,需要混合顏色計算。下面來說說解決這個問題的方法
- (2.)UILabel:中如果顯示的文本是英文你會發現顯示的是綠色,然而換成中文后居然顯示的是紅色。
-
(3.)對于圖片中有透明區域,這就需要根據界面與設計同學進行調整。雖然現在的處理器越來越強,這些優化微不足道,但對于一個合格的程序員而言,盡善盡美才是追求
-
(4.)異步加載繪制
知識點:對象的創建,屬性的調整等都比較消耗CPU,所以盡量的使用輕量級的對象可以減少CPU的消耗,而CALayer的量級比UIVIew輕許多。所以數據或對象的創建盡量放在異步線程中執行,保證主線程的暢通快速。但包含CALayer的控件都必須在主線程中創建操作,而界面控件一般都是在viewDidLoad里創建的,而系統方法都是在主線程中執行的,具體原因這里可以要說說Runloop的原理,過段時間寫一篇關于Runloop原理的文章說明吧。
(5.)界面的數據采用異步線程的方式去計算配置,當界面數據都配置完全了,在回到主線程中去設置UI
(6.)在很多時候界面的數據我會需要從網絡中獲取,而有時多個網絡請求之間沒有關聯關系,我們可以采用信號量的方式,去同步請求網絡數據,當所有網絡數據都返回后,在開始計算配置數據
(7.)通過Storyboard創建的視圖對象消耗的資源比純代碼創建對象要多很多
(8.)Block回調來異步執行任務回調(Block是個很神奇的東西,要靈活應用啊)
-
(9.)關于TableView的優化請看我另外一篇文章UITableView的性能優化
-
(10.)有次跟朋友討論優化的時候,說道為什么微博內容多也復雜,流暢度這么高。我們改用的方法都用了,但是cell內部內容一復雜幀數就開始下降了。后來才知道,原來是自動布局的鍋,再加上自己對文本內容認識深度不夠。布局是非常好性能資源的,有時為了性能少用Autolayouer技術和UILabel(但實際情況好像不可能,哇咔咔)。那么選擇一個好的自動布局第三方尤為重要了。微博可能是有一套非開源的布局方法吧(這里有個來自百度知道團隊的開源項目可以看看代碼學習學習:FDTemplateLayoutCell。)
-
(11.)圖片的縮放,UIImageView的尺寸最好跟Bundle里的原圖大小,因為圖片的縮放是非常耗性能的。在實際開發中,需要適配不同的屏幕尺寸,這個時候就需要與設計大神們好好溝通了。我們常在開發適配的時候,會寫一個比例尺寸,界面在不同屏幕下的尺寸都是按照這個比例縮放的。所以要把自己的比例告訴設計大神們才能達到不縮放。
如果還是會縮放,那么你就需要異步去把圖片繪制成UIImageView大小的圖片了
/**根據邊距拉伸圖片@param sourceImage 原圖片@param edgeInsets 邊距@param resizingMode 縮放模式@param size 需要拉伸的大小@param block 處理后的圖片*/ +(void)imageCompress:(UIImage *)sourceImage forEdgeInsets:(UIEdgeInsets)edgeInsets resizingMode:(UIImageResizingMode)resizingMode forSize:(CGSize)size Block:(void (^)(UIImage *image))block { /*異步處理*/ dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *Image; Image = [sourceImage resizableImageWithCapInsets:edgeInsets resizingMode:resizingMode]; UIGraphicsBeginImageContext(CGSizeMake(size.width, size.height)); [Image drawInRect:CGRectMake(0,0,size.width, size.height)]; UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (block) { /* 回到主線程 */ dispatch_async(dispatch_get_main_queue(), ^{ block(newImage); }); } }); }-
(12.)避免不必要的圖片緩存:通常我們會用imageNamed:來加載圖片,但是這個方法會對圖片進行緩存。對于一些只有特定界面才有不常用的圖片用這個方法會造成一定的內存消耗,一般不常用的圖片采用initWithContentsOfFile:。可以自己寫一個UIImage的類別,自行判斷使用哪一個方法。這個方法我有用過,但可能目前處理器性能太好或者設計同學的圖片本身就很小,內存上并看不出多大差別。對于那些圖片為主的App這個方法還是很有用的
-
(13.)減少文件讀取次數:文件的讀取是是否消耗資源的,所以在沒有必要分開文件內容的情況下,盡量把內容放在一個文件中,減少消耗。例如圖片的讀取,第一種,多個標簽圖片放在一個圖片中,然后根據圖片進行區域繪制,這樣就減少了對圖片的讀取時消耗CPU的性能,第二種shouldRasterize光柵化,在GPU渲染時,直接取出上次的繪制內容,來減少文件的讀取和重新繪制。
作者:樹下敲代碼的超人
鏈接:https://www.jianshu.com/p/2efcf7ad2608
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
轉載于:https://www.cnblogs.com/moondev/p/10201669.html
總結
以上是生活随笔為你收集整理的ios 性能优化(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MariaDB 主从同步与热备(14)
- 下一篇: [Golang学习笔记] 05 程序实体