自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

iOS 之如何利用 RunLoop 原理去監(jiān)控卡頓?

移動(dòng)開(kāi)發(fā) iOS
通過(guò) Runloop 來(lái)檢測(cè)卡頓,還是很有必要的。對(duì)提高 app 的用戶使用體驗(yàn)還是很有幫助的。畢竟卡頓是偶顯的不容易復(fù)現(xiàn)。所以檢測(cè)卡頓來(lái)來(lái)抓取堆棧信息,分析并解決卡頓,還是很有必要的。

[[397057]]

1. 前言

卡頓問(wèn)題,就是在主線程上無(wú)法響應(yīng)用戶交互的問(wèn)題。如果一個(gè) App 時(shí)不時(shí)地就給你卡一下,有 時(shí)還長(zhǎng)時(shí)間無(wú)響應(yīng),這時(shí)你還愿意繼續(xù)用它嗎?所以說(shuō),卡頓問(wèn)題對(duì) App 的傷害是巨大的,也是 我們必須要重點(diǎn)解決的一個(gè)問(wèn)題。

2. 卡頓原因

現(xiàn)在,我們先來(lái)看一下導(dǎo)致卡頓問(wèn)題的幾種原因:

  • 復(fù)雜 UI 、圖文混排的繪制量過(guò)大;
  • 在主線程上做網(wǎng)絡(luò)同步請(qǐng)求;
  • 在主線程做大量的 IO 操作;
  • 運(yùn)算量過(guò)大,CPU 持續(xù)高占用;
  • 死鎖和主子線程搶鎖。

那么,我們?nèi)绾伪O(jiān)控到什么時(shí)候會(huì)出現(xiàn)卡頓呢?是要監(jiān)視FPS嗎?

FPS 是一秒顯示的幀數(shù),也就是一秒內(nèi)畫(huà)面變化數(shù)量。當(dāng)FPS達(dá)到60,說(shuō)明界面很流程,當(dāng)FPS低于24,頁(yè)面流暢度不是那么流暢,但是不能說(shuō)卡主了。

由此可見(jiàn),簡(jiǎn)單地通過(guò)監(jiān)視 FPS 是很難確定是否會(huì)出現(xiàn)卡頓問(wèn)題了,所以我就果斷棄了通過(guò)監(jiān)視 FPS 來(lái)監(jiān)控卡頓的方案。

那么,我們到底應(yīng)該使用什么方案來(lái)監(jiān)控卡頓呢?

3. 使用RunLoop來(lái)檢控卡頓

對(duì)于 iOS 開(kāi)發(fā)來(lái)說(shuō),監(jiān)控卡頓就是要去找到主線程上都做了哪些事兒。我們都知道,線程的消息 事件是依賴于 NSRunLoop 的,所以從 NSRunLoop 入手,就可以知道主線程上都調(diào)用了哪些方 法。我們通過(guò)監(jiān)聽(tīng) NSRunLoop 的狀態(tài),就能夠發(fā)現(xiàn)調(diào)用方法是否執(zhí)行時(shí)間過(guò)長(zhǎng),從而判斷出是 否會(huì)出現(xiàn)卡頓。

所以,我推薦的監(jiān)控卡頓的方案是:通過(guò)監(jiān)控 RunLoop 的狀態(tài)來(lái)判斷是否會(huì)出現(xiàn)卡頓。

3.1 Runloop

RunLoop是iOS開(kāi)發(fā)中的一個(gè)基礎(chǔ)概念,為了幫助你理解并用好這個(gè)對(duì)象,接下來(lái)我會(huì)先和你介紹一下它可以做哪些事兒,以及它為什么可以做成這些事兒。

RunLoop 這個(gè)對(duì)象,在 iOS 里由 CFRunLoop 實(shí)現(xiàn)。簡(jiǎn)單來(lái)說(shuō),RunLoop 是用來(lái)監(jiān)聽(tīng)輸入源,進(jìn) 行調(diào)度處理的。這里的輸入源可以是輸入設(shè)備、網(wǎng)絡(luò)、周期性或者延遲時(shí)間、異步回調(diào)。

RunLoop 會(huì)接收兩種類(lèi)型的輸入源:

  • 一種是來(lái)自另一個(gè)線程或者來(lái)自不同應(yīng)用的異步消息;
  • 另一 種是來(lái)自預(yù)訂時(shí)間或者重復(fù)間隔的同步事件。

RunLoop 的目的是,當(dāng)有事件要去處理時(shí)保持線程忙,當(dāng)沒(méi)有事件要處理時(shí)讓線程進(jìn)入休眠。所 以,了解 RunLoop 原理不光能夠運(yùn)用到監(jiān)控卡頓上,還可以提高用戶的交互體驗(yàn)。通過(guò)將那些 繁重而不緊急會(huì)大量占用 CPU 的任務(wù)(比如圖片加載),放到空閑的 RunLoop 模式里執(zhí)行,這 樣就可以避開(kāi)在 UITrackingRunLoopMode 這個(gè) RunLoop 模式時(shí)是執(zhí)行。

UITrackingRunLoopMode 是用戶進(jìn)行滾動(dòng)操作時(shí)會(huì)切換到的 RunLoop 模式,避免在這個(gè) RunLoop 模式執(zhí)行繁重的 CPU 任務(wù),就能避免影響用戶交互操作上體驗(yàn)。

接下來(lái),我就通過(guò) CFRunLoop 的源碼來(lái)跟你分享下 RunLoop 的原理吧。

 3.2 RunLoop原理

其內(nèi)部代碼整理如下:

  1. /// 用DefaultMode啟動(dòng) 
  2. void CFRunLoopRun(void) { 
  3.     CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); 
  4.   
  5. /// 用指定的Mode啟動(dòng),允許設(shè)置RunLoop超時(shí)時(shí)間 
  6. int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { 
  7.     return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); 
  8.   
  9. /// RunLoop的實(shí)現(xiàn) 
  10. int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { 
  11.      
  12.     /// 首先根據(jù)modeName找到對(duì)應(yīng)mode 
  13.     CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); 
  14.     /// 如果mode里沒(méi)有source/timer/observer, 直接返回。 
  15.     if (__CFRunLoopModeIsEmpty(currentMode)) return
  16.      
  17.     /// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。 
  18.     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); 
  19.      
  20.     /// 內(nèi)部函數(shù),進(jìn)入loop 
  21.     __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { 
  22.          
  23.         Boolean sourceHandledThisLoop = NO
  24.         int retVal = 0; 
  25.         do { 
  26.   
  27.             /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。 
  28.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 
  29.             /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。 
  30.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); 
  31.             /// 執(zhí)行被加入的block 
  32.             __CFRunLoopDoBlocks(runloop, currentMode); 
  33.              
  34.             /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。 
  35.             sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); 
  36.             /// 執(zhí)行被加入的block 
  37.             __CFRunLoopDoBlocks(runloop, currentMode); 
  38.   
  39.             /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。 
  40.             if (__Source0DidDispatchPortLastTime) { 
  41.                 Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) 
  42.                 if (hasMsg) goto handle_msg; 
  43.             } 
  44.              
  45.             /// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。 
  46.             if (!sourceHandledThisLoop) { 
  47.                 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); 
  48.             } 
  49.              
  50.             /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。 
  51.             /// • 一個(gè)基于 port 的Source 的事件。 
  52.             /// • 一個(gè) Timer 到時(shí)間了 
  53.             /// • RunLoop 自身的超時(shí)時(shí)間到了 
  54.             /// • 被其他什么調(diào)用者手動(dòng)喚醒 
  55.             __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { 
  56.                 mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg 
  57.             } 
  58.   
  59.             /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。 
  60.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); 
  61.              
  62.             /// 收到消息,處理消息。 
  63.             handle_msg: 
  64.   
  65.             /// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。 
  66.             if (msg_is_timer) { 
  67.                 __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) 
  68.             }  
  69.   
  70.             /// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。 
  71.             else if (msg_is_dispatch) { 
  72.                 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 
  73.             }  
  74.   
  75.             /// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件 
  76.             else { 
  77.                 CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); 
  78.                 sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); 
  79.                 if (sourceHandledThisLoop) { 
  80.                     mach_msg(reply, MACH_SEND_MSG, reply); 
  81.                 } 
  82.             } 
  83.              
  84.             /// 執(zhí)行加入到Loop的block 
  85.             __CFRunLoopDoBlocks(runloop, currentMode); 
  86.              
  87.   
  88.             if (sourceHandledThisLoop && stopAfterHandle) { 
  89.                 /// 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。 
  90.                 retVal = kCFRunLoopRunHandledSource; 
  91.             } else if (timeout) { 
  92.                 /// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了 
  93.                 retVal = kCFRunLoopRunTimedOut; 
  94.             } else if (__CFRunLoopIsStopped(runloop)) { 
  95.                 /// 被外部調(diào)用者強(qiáng)制停止了 
  96.                 retVal = kCFRunLoopRunStopped; 
  97.             } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { 
  98.                 /// source/timer/observer一個(gè)都沒(méi)有了 
  99.                 retVal = kCFRunLoopRunFinished; 
  100.             } 
  101.              
  102.             /// 如果沒(méi)超時(shí),mode里沒(méi)空,loop也沒(méi)被停止,那繼續(xù)loop。 
  103.         } while (retVal == 0); 
  104.     } 
  105.      
  106.     /// 10. 通知 Observers: RunLoop 即將退出。 
  107.     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 

可以看到,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù),其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。

RunLoop內(nèi)部的邏輯圖:

RunLoop內(nèi)部原理.png

4. 如何檢測(cè)卡頓

4.1 首先知道RunLoop的六個(gè)狀態(tài)

  1. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  2.     kCFRunLoopEntry         = (1UL << 0), // 即將進(jìn)入Loop 
  3.     kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer 
  4.     kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source 
  5.     kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠 
  6.     kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒 
  7.     kCFRunLoopExit          = (1UL << 7), // 即將退出Loop 
  8. kCFRunLoopAllActivities // loop所有狀態(tài)改變 
  9.  
  10. }; 

要想監(jiān)聽(tīng)RunLoop,你就首先需要?jiǎng)?chuàng)建一個(gè) CFRunLoopObserverContext 觀察者,代碼如下:

  1. - (void)registerObserver { 
  2.      
  3.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  4.     //創(chuàng)建Run loop observer對(duì)象 
  5.     //第一個(gè)參數(shù)用于分配observer對(duì)象的內(nèi)存 
  6.     //第二個(gè)參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見(jiàn)回調(diào)函數(shù)myRunLoopObserver中注釋 
  7.     //第三個(gè)參數(shù)用于標(biāo)識(shí)該observer是在第一次進(jìn)入run loop時(shí)執(zhí)行還是每次進(jìn)入run loop處理時(shí)均執(zhí)行 
  8.     //第四個(gè)參數(shù)用于設(shè)置該observer的優(yōu)先級(jí) 
  9.     //第五個(gè)參數(shù)用于設(shè)置該observer的回調(diào)函數(shù) 
  10.     //第六個(gè)參數(shù)用于設(shè)置該observer的運(yùn)行環(huán)境 
  11.     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  12.                                                             kCFRunLoopAllActivities, 
  13.                                                             YES, 
  14.                                                             0, 
  15.                                                             &runLoopObserverCallBack, 
  16.                                                             &context); 
  17.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  18.  

實(shí)時(shí)獲取變化的回調(diào)的方法:

  1. //每當(dāng)runloop狀態(tài)變化的觸發(fā)這個(gè)回調(diào)方法 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     MyClass *object = (__bridge MyClass*)info; 
  4.     object->activity = activity; 

其中UI主要集中在

_CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(source0)和CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION(source1)之前。

獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態(tài)就可以知道是否有卡頓的情況。

4.2 檢測(cè)卡頓的思路

只需要另外再開(kāi)啟一個(gè)線程,實(shí)時(shí)計(jì)算這兩個(gè)狀態(tài)區(qū)域之間的耗時(shí)是否到達(dá)某個(gè)閥值,便能揪出這些性能殺手。

  • 監(jiān)聽(tīng)runloop狀態(tài)變化回調(diào)方法
  1. // 就是runloop有一個(gè)狀態(tài)改變 就記錄一下 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     BGPerformanceMonitor *monitor = (__bridge BGPerformanceMonitor*)info; 
  4.      
  5.     // 記錄狀態(tài)值 
  6.     monitor->activity = activity; 
  7.      
  8.     // 發(fā)送信號(hào) 
  9.     dispatch_semaphore_t semaphore = monitor->semaphore; 
  10.     long st = dispatch_semaphore_signal(semaphore); 
  11.     NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[BGPerformanceMonitor getCurTime]); 
  12.      
  13.  
  14.        /* Run Loop Observer Activities */ 
  15. //    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  16. //        kCFRunLoopEntry = (1UL << 0),    // 進(jìn)入RunLoop循環(huán)(這里其實(shí)還沒(méi)進(jìn)入) 
  17. //        kCFRunLoopBeforeTimers = (1UL << 1),  // RunLoop 要處理timer了 
  18. //        kCFRunLoopBeforeSources = (1UL << 2), // RunLoop 要處理source了 
  19. //        kCFRunLoopBeforeWaiting = (1UL << 5), // RunLoop要休眠了 
  20. //        kCFRunLoopAfterWaiting = (1UL << 6),   // RunLoop醒了 
  21. //        kCFRunLoopExit = (1UL << 7),           // RunLoop退出(和kCFRunLoopEntry對(duì)應(yīng)) 
  22. //        kCFRunLoopAllActivities = 0x0FFFFFFFU //RunLoop狀態(tài)變化 
  23. //    }; 
  24.     if (activity == kCFRunLoopEntry) {  // 即將進(jìn)入RunLoop 
  25.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry"); 
  26.     } else if (activity == kCFRunLoopBeforeTimers) {    // 即將處理Timer 
  27.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers"); 
  28.     } else if (activity == kCFRunLoopBeforeSources) {   // 即將處理Source 
  29.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources"); 
  30.     } else if (activity == kCFRunLoopBeforeWaiting) {   //即將進(jìn)入休眠 
  31.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting"); 
  32.     } else if (activity == kCFRunLoopAfterWaiting) {    // 剛從休眠中喚醒 
  33.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting"); 
  34.     } else if (activity == kCFRunLoopExit) {    // 即將退出RunLoop 
  35.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit"); 
  36.     } else if (activity == kCFRunLoopAllActivities) { 
  37.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities"); 
  38.     } 
  • 開(kāi)啟runloop監(jiān)聽(tīng)
  1. // 開(kāi)始監(jiān)聽(tīng) 
  2. - (void)startMonitor { 
  3.     if (observer) { 
  4.         return
  5.     } 
  6.      
  7.     // 創(chuàng)建信號(hào) 
  8.     semaphore = dispatch_semaphore_create(0); 
  9.     NSLog(@"dispatch_semaphore_create:%@",[BGPerformanceMonitor getCurTime]); 
  10.      
  11.     // 注冊(cè)RunLoop狀態(tài)觀察 
  12.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  13.     //創(chuàng)建Run loop observer對(duì)象 
  14.     //第一個(gè)參數(shù)用于分配observer對(duì)象的內(nèi)存 
  15.     //第二個(gè)參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見(jiàn)回調(diào)函數(shù)myRunLoopObserver中注釋 
  16.     //第三個(gè)參數(shù)用于標(biāo)識(shí)該observer是在第一次進(jìn)入run loop時(shí)執(zhí)行還是每次進(jìn)入run loop處理時(shí)均執(zhí)行 
  17.     //第四個(gè)參數(shù)用于設(shè)置該observer的優(yōu)先級(jí) 
  18.     //第五個(gè)參數(shù)用于設(shè)置該observer的回調(diào)函數(shù) 
  19.     //第六個(gè)參數(shù)用于設(shè)置該observer的運(yùn)行環(huán)境 
  20.     observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  21.                                        kCFRunLoopAllActivities, 
  22.                                        YES, 
  23.                                        0, 
  24.                                        &runLoopObserverCallBack, 
  25.                                        &context); 
  26.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  27.      
  28.     // 在子線程監(jiān)控時(shí)長(zhǎng) 
  29.     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
  30.         while (YES) {   // 有信號(hào)的話 就查詢當(dāng)前runloop的狀態(tài) 
  31.             // 假定連續(xù)5次超時(shí)50ms認(rèn)為卡頓(當(dāng)然也包含了單次超時(shí)250ms) 
  32.             // 因?yàn)橄旅?nbsp;runloop 狀態(tài)改變回調(diào)方法runLoopObserverCallBack中會(huì)將信號(hào)量遞增 1,所以每次 runloop 狀態(tài)改變后,下面的語(yǔ)句都會(huì)執(zhí)行一次 
  33.             // dispatch_semaphore_wait:Returns zero on success, or non-zero if the timeout occurred. 
  34.             long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); 
  35.             NSLog(@"dispatch_semaphore_wait:st=%ld,time:%@",st,[self getCurTime]); 
  36.             if (st != 0) {  // 信號(hào)量超時(shí)了 - 即 runloop 的狀態(tài)長(zhǎng)時(shí)間沒(méi)有發(fā)生變更,長(zhǎng)期處于某一個(gè)狀態(tài)下 
  37.                 if (!observer) { 
  38.                     timeoutCount = 0; 
  39.                     semaphore = 0; 
  40.                     activity = 0; 
  41.                     return
  42.                 } 
  43.                 NSLog(@"st = %ld,activity = %lu,timeoutCount = %d,time:%@",st,activity,timeoutCount,[self getCurTime]); 
  44.                 // kCFRunLoopBeforeSources - 即將處理source kCFRunLoopAfterWaiting - 剛從休眠中喚醒 
  45.                 // 獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態(tài)就可以知道是否有卡頓的情況。 
  46.                 // kCFRunLoopBeforeSources:停留在這個(gè)狀態(tài),表示在做很多事情 
  47.                 if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {    // 發(fā)生卡頓,記錄卡頓次數(shù) 
  48.                     if (++timeoutCount < 5) { 
  49.                         continue;   // 不足 5 次,直接 continue 當(dāng)次循環(huán),不將timeoutCount置為0 
  50.                     } 
  51.                      
  52.                      
  53.                     // 收集Crash信息也可用于實(shí)時(shí)獲取各線程的調(diào)用堆棧 
  54.                     PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  55.                      
  56.                     PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  57.                      
  58.                     NSData *data = [crashReporter generateLiveReport]; 
  59.                     PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  60.                     NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS]; 
  61.                      
  62.                     NSLog(@"---------卡頓信息\n%@\n--------------",report); 
  63.                 } 
  64.             } 
  65.             NSLog(@"dispatch_semaphore_wait timeoutCount = 0,time:%@",[self getCurTime]); 
  66.             timeoutCount = 0; 
  67.         } 
  68.     }); 

記錄卡頓的函數(shù)調(diào)用

監(jiān)控到了卡頓現(xiàn)場(chǎng),當(dāng)然下一步便是記錄此時(shí)的函數(shù)調(diào)用信息,此處可以使用一個(gè)第三方Crash 收集組件 PLCrashReporter,它不僅可以收集 Crash 信息也可用于實(shí)時(shí)獲取各線程的調(diào)用堆棧,示例如下:

  1. PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD 
  2.                                                                    symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  3. PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  4. NSData *data = [crashReporter generateLiveReport]; 
  5. PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  6. NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter 
  7.                                                           withTextFormat:PLCrashReportTextFormatiOS]; 
  8. NSLog(@"------------\n%@\n------------", report); 

當(dāng)檢測(cè)到卡頓時(shí),抓取堆棧信息,然后在客戶端做一些過(guò)濾處理,便可以上報(bào)到服務(wù)器,通過(guò)收集一定量的卡頓數(shù)據(jù)后經(jīng)過(guò)分析便能準(zhǔn)確定位需要優(yōu)化的邏輯,這個(gè)實(shí)時(shí)卡頓監(jiān)控就大功告成了!

5. 結(jié)尾

 

通過(guò) Runloop 來(lái)檢測(cè)卡頓,還是很有必要的。對(duì)提高 app 的用戶使用體驗(yàn)還是很有幫助的。畢竟卡頓是偶顯的不容易復(fù)現(xiàn)。所以檢測(cè)卡頓來(lái)來(lái)抓取堆棧信息,分析并解決卡頓,還是很有必要的。

 

責(zé)任編輯:武曉燕 來(lái)源: 網(wǎng)羅開(kāi)發(fā)
相關(guān)推薦

2013-03-27 10:32:53

iOS多線程原理runloop介紹GCD

2018-07-27 18:47:01

數(shù)據(jù)庫(kù)MySQL線程

2021-08-03 16:35:04

AndroidANR內(nèi)存

2011-08-15 14:27:51

CocoaRunLoop

2021-04-02 14:23:12

WiFi網(wǎng)絡(luò)技術(shù)

2021-03-31 21:20:15

WiFi網(wǎng)絡(luò)漫游

2023-02-03 15:14:15

2017-11-21 09:25:23

2021-03-15 10:31:48

手機(jī)安卓蘋(píng)果

2021-08-31 23:09:50

微信功能技巧

2021-11-28 21:26:39

Windows 7Windows微軟

2017-03-02 12:39:04

移動(dòng)端iOS監(jiān)控體系

2015-06-03 16:33:23

手機(jī)銀行應(yīng)用性能APP

2013-12-27 10:37:01

2019-07-01 15:46:35

云平臺(tái)Kubernetes問(wèn)題排查

2022-05-02 08:30:46

網(wǎng)絡(luò)Wi-Fi

2012-08-20 09:45:18

SQL Server

2011-03-25 15:01:26

Cacti監(jiān)控memcache

2011-03-29 15:35:14

cactimemcache

2011-04-02 12:52:37

MRTG監(jiān)控
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)