?hi~ iOS行業(yè)的小伙伴們? 大家好 :

(小伙伴們:不忘初心 方得始終 為了生活 不要頹廢 請努力拼搏?。? ? ? ?
? ? ? 相信對于做APP的小伙伴來說,時不時會收到用戶的卡頓反饋,比如:"某個用戶碰到從后臺切換前臺卡了一下,最近偶爾會遇到幾次";"某個用戶反饋點一個按鈕或者對話框,程序卡了好幾秒";"某個用戶反饋切換 tab時 很卡"等等。??
? ? 那么界面卡頓是由哪些原因導致的呢?

? ? ?死鎖:主線程拿到鎖 A,需要獲得鎖 B,而同時某個子線程拿了鎖 B,需要鎖 A,這樣相互等待就死鎖了。
? ? ?搶鎖:主線程需要訪問 DB,而此時某個子線程往 DB 插入大量數(shù)據(jù)。通常搶鎖的體驗是偶爾卡一陣子,過會就恢復了。
? ? ? 主線程大量 IO:主線程為了方便直接寫入大量數(shù)據(jù),會導致界面卡頓。
? ? ? 主線程大量計算:算法不合理,導致主線程某個函數(shù)占用大量CPU。
? ? ? 大量的 UI 繪制:復雜的 UI、圖文混排等,帶來大量的 UI 繪制。
? ? 那怎么確定這些問題呢?

? ? ?死鎖一般會伴隨 crash,可以通過 crash report 來分析。
? ? ?搶鎖不好辦,將鎖等待時間打出來用處不大,我們還需要知道是誰占了鎖。
? ? ?大量 IO 可以在函數(shù)開始結束打點,將占用時間打到日志中。
? ? ?大量計算同理可以將耗時打到日志中。
? ? ?大量 UI 繪制一般是必現(xiàn),還好辦;如果是偶現(xiàn)的話,想加日志點都沒地方,因為是慢在系統(tǒng)函數(shù)里面。
? ? ? 如果可以將當時的線程堆棧捕捉下來,那么上述難題都迎刃而解。主線程在什么函數(shù)哪一行卡住,在等什么鎖,而這個鎖又是被哪個子線程的哪個函數(shù)占用,有了堆棧,就可以知道。是慢在UI繪制,還是慢在我們的代碼。
? ? 解決方案

? ? ?在移動設備上開發(fā)軟件,性能一直是我們最為關心的話題之一,我們作為程序員除了需要努力提高代碼質量之外,及時發(fā)現(xiàn)和監(jiān)控軟件中那些造成性能低下的”罪魁禍首”也是我們神圣的職責。
? ? 眾所周知,iOS平臺因為UIKit本身的特性,需要將所有的UI操作都放在主線程執(zhí)行,所以也造成不少程序員都習慣將一些線程安全性不確定的邏輯,以及其它線程結束后的匯總工作等等放到了主線,所以主線程中包含的這些大量計算、IO、繪制都有可能造成卡頓。
? ? ?在Xcode中已經(jīng)集成了非常方便的調試工具Instruments,它可以幫助我們在開發(fā)測試階段分析軟件運行的性能消耗,但一款軟件經(jīng)過測試流程和實驗室分析肯定是不夠的,在正式環(huán)境中由大量用戶在使用過程中監(jiān)控、分析到的數(shù)據(jù)更能解決一些隱藏的問題。
? ? 尋找卡頓的切入點監(jiān)控卡頓,最直接就是找到主線程都在干些啥玩意兒.我們知道一個線程的消息事件處理都是依賴于NSRunLoop來驅動,所以要知道線程正在調用什么方法,就需要從NSRunLoop來入手.CFRunLoop的代碼是開源,其中核心方法CFRunLoopRun簡化后的主要邏輯大概是這樣的:

? ?不難發(fā)現(xiàn)NSRunLoop調用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之間,還有kCFRunLoopAfterWaiting之后,也就是如果我們發(fā)現(xiàn)這兩個時間內耗時太長,那么就可以判定出此時主線程卡頓.
? ?量化卡頓的程度
? ?要監(jiān)控NSRunLoop的狀態(tài),我們需要使用到CFRunLoopObserverRef,通過它可以實時獲得這些狀態(tài)值的變化,具體的使用如下:


? ? 只需要另外再開啟一個線程,實時計算這兩個狀態(tài)區(qū)域之間的耗時是否到達某個閥值,便能揪出這些性能殺手.
? ? 為了讓計算更精確,需要讓子線程更及時的獲知主線程NSRunLoop狀態(tài)變化,所以dispatch_semaphore_t是個不錯的選擇,另外卡頓需要覆蓋到多次連續(xù)小卡頓和單次長時間卡頓兩種情景,所以判定條件也需要做適當優(yōu)化.將上面兩個方法添加計算的邏輯如下:


? 記錄卡頓的函數(shù)調用
? ? 監(jiān)控到了卡頓現(xiàn)場,當然下一步便是記錄此時的函數(shù)調用信息,此處可以使用一個第三方Crash收集組件PLCrashReporter,它不僅可以收集Crash信息也可用于實時獲取各線程的調用堆棧,使用示例如下:
PLCrashReporterConfig?config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD
symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter?
crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
NSData?data = [crashReporter generateLiveReport];
PLCrashReport?
reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter
withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"------------\n%@\n------------", report);
? ? ?當檢測到卡頓時,抓取堆棧信息,然后在客戶端做一些過濾處理,便可以上報到服務器,通過收集一定量的卡頓數(shù)據(jù)后經(jīng)過分析便能準確定位需要優(yōu)化的邏輯,至此這個實時卡頓監(jiān)控就大功告成了!

? ?小伙伴們:其實做為一個開發(fā)者,有一個學習的氛圍跟一個交流圈子特別重要!?。?/b>
擴大圈子,技術交流,學習提升!
↓ (機會永遠是留給那些有準備的人) ?↓? ? ? ?
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
相關閱讀更多精彩內容
- 1.ios高性能編程 (1).內層 最小的內層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結構(3).初始化時...
- 前言 在移動設備上開發(fā)軟件,性能一直是我們最為關心的話題之一,我們作為程序員除了需要努力提高代碼質量之外,及時發(fā)現(xiàn)...