前言緩存
前些時間有好幾個技術朋友問過筆者相似的問題:主線程須要執行大量的任務致使卡頓如何處理?異步任務量級過大致使 CPU 和內存壓力太高如何優化?安全
解決相似的問題能夠用幾個思路:降頻、淘汰、優先級調度。性能優化
原本解決這些問題並不須要很複雜的代碼,可是涉及到一些 C 代碼而且要注意線程安全的問題,因此筆者就作了這樣一個輪子,以解決任務調度引起的性能問題。數據結構
本文講述YBTaskScheduler的原理,讀者朋友須要有必定的 iOS 基礎,瞭解一些性能優化的知識,基本用法能夠先看看 GitHubREADME,DEMO 中也有一個相冊列表的應用案例。多線程
1、需求分析異步
就拿 DEMO 中的案例來講明,一個顯示相冊圖片的列表:函數
實現圖中業務,必然考慮到幾個耗時操做:oop
從相冊讀取圖片性能
解壓圖片優化
圓角處理
繪製圖片
理所固然的想處處理方案(DEMO中有實現):
異步讀取圖片
異步裁剪圖片爲正方形(這個過程當中就解壓了)
異步裁剪圓角
回到主線程繪製圖片
一整套流程下來,貌似需求很好的解決了,可是當快速滑動列表時,會發現 CPU 和內存的佔用會比較高(這取決於從相冊中讀取並顯示多大的圖片)。固然 DEMO 中按照屏幕的物理像素處理,就算不使用任務調度器組件快速滑動列表也基本不會有掉幀的現象。考慮到老舊設備或者技術人員的水平,不少時候這種需求會致使嚴重的 CPU 和內存負擔,甚至致使閃退。
以上處理方案可能存在的性能瓶頸:
從相冊讀取圖片、裁剪圖片,處理圓角、主線程繪製等操做會致使 CPU 計算壓力過大。
同時解壓的圖片、同時繪製的圖片過多致使內存峯值飆升(更不要說作了圖片的緩存)。
任何一種狀況均可能致使客戶端卡死或者閃退,結合業務來分析問題,會發現優化的思路仍是不難找到:
滑出屏幕的圖片不會存在繪製壓力,而當前屏幕中的圖片會在一個 RunLoop 循環週期繪製,可能形成掉幀。因此能夠減小一個 RunLoop 循環週期所繪製的圖片數量。
快速滑動列表,大量的異步任務直接交由 CPU 執行,然而滑出屏幕的圖片已經沒有處理它的意義了。因此能夠提早刪除掉已經滑出屏幕的異步任務,以此來下降 CPU 和內存壓力。
沒錯,YBTaskScheduler組件就是替你作了這些事情 ,並且還不止於此。
2、命令模式與 RunLoop
想要管理這些複雜的任務,而且在合適的時機調用它們,天然而然的就想到了命令模式。意味着任務不能直接執行,而是把任務做爲一個命令裝入容器。
在 Objective-C 中,顯然 Block 代碼塊能解決延遲執行這個問題:
[_scheduler addTask:^{/*
具體任務代碼
解壓圖片、裁剪圖片、訪問磁盤等
*/}];
複製代碼
而後組件將這些代碼塊「裝起來」,組件由此「掌握」了全部的任務,能夠自由的決定什麼時候調用這些代碼塊,什麼時候對某些代碼塊進行淘汰,還能夠實現優先級調度。
既然是命令模式,還差一個 Invoker (調用程序),即什麼時候去觸發這些任務。結合 iOS 的技術特色,能夠監聽 RunLoop 循環週期來實現:
staticvoidaddRunLoopObserver() {staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{ taskSchedulers = [NSHashTableweakObjectsHashTable];CFRunLoopObserverRefobserver =CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit,true,0xFFFFFF, runLoopObserverCallBack,NULL);CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);CFRelease(observer); });}
而後在回調函數中進行任務的調度。
3、策略模式
考慮到任務的淘汰策略和優先級調度,必然須要一些高效數據結構來支撐,爲了提升處理效率,筆者直接使用了 C++ 的數據結構:deque和priority_queue。
由於要實現任務淘汰,因此使用deque雙端隊列來模擬棧和隊列,而不是直接使用stack和queue。使用priority_queue優先隊列來處理自定義的優先級調度,它的缺點是不能刪除低優先級節點,爲了節約時間成本姑且夠用。
具體的策略:
棧:後加入的任務先執行(能夠理解爲後加入的任務優先級高),優先淘汰先加入的任務。
隊列:先加入的任務先執行(能夠理解爲先加入的任務優先級高),優先淘汰後加入的任務。
優先隊列:自定義任務優先級,不支持任務淘汰。
實際上組件是推薦使用棧和隊列這兩種策略,由於插入和取出的時間複雜度是常數級的,須要定製任務的優先級時才考慮使用優先隊列,由於其插入複雜度是 O(logN) 的。
至此,整個組件的業務是比較清晰了,組件須要讓這三種處理方式能夠自由的變更,因此採用策略模式來處理,下面是 UML 類圖:
UML類圖
嗯,這是個挺標準的策略模式。
4、線程安全
因爲任務的調度可能在任意線程,因此必需要作好容器(棧、隊列、優先隊列)訪問的線程安全問題,組件是使用pthread_mutex_t和dispatch_once來保證線程安全,同時筆者儘可能減小臨界區來提升性能。值得注意的是,若是不會存在線程安全的代碼就不要去加鎖了。
關於多線程及安全能夠看筆者的另外一篇文章:iOS 如何高效的使用多線程,這裏就不贅述了。
後語
部分技術細節就很少說了,組件代碼量比較少,若是感興趣能夠直接看源碼。實際上這個組件的應用場景並非不少,在項目穩定須要作深度的性能優化時可能會比較須要它,而且但願使用它的人也能瞭解一些原理,作到成竹在胸,才能靈活的運用。
本文源爲第三方轉載,原文連接:www.jianshu.com/p/f2a610c77…
文章如有不對地方,歡迎批評指正,一個小而有用QQ交流羣:805558511