iOS實(shí)時(shí)卡頓監(jiān)控

參考:http://www.tanhao.me/code/151113.html/

在移動(dòng)設(shè)備上開(kāi)發(fā)軟件,性能一直是我們最為關(guān)心的話題之一,我們作為程序員除了需要努力提高代碼質(zhì)量之外,及時(shí)發(fā)現(xiàn)和監(jiān)控軟件中那些造成性能低下的”罪魁禍?zhǔn)住币彩俏覀兩袷サ穆氊?zé).

眾所周知,iOS平臺(tái)因?yàn)閁IKit本身的特性,需要將所有的UI操作都放在主線程執(zhí)行,所以也造成不少程序員都習(xí)慣將一些線程安全性不確定的邏輯,以及其它線程結(jié)束后的匯總工作等等放到了主線,所以主線程中包含的這些大量計(jì)算、IO、繪制都有可能造成卡頓.

在Xcode中已經(jīng)集成了非常方便的調(diào)試工具Instruments,它可以幫助我們?cè)陂_(kāi)發(fā)測(cè)試階段分析軟件運(yùn)行的性能消耗,但一款軟件經(jīng)過(guò)測(cè)試流程和實(shí)驗(yàn)室分析肯定是不夠的,在正式環(huán)境中由大量用戶在使用過(guò)程中監(jiān)控、分析到的數(shù)據(jù)更能解決一些隱藏的問(wèn)題.

尋找卡頓的切入點(diǎn)

監(jiān)控卡頓,最直接就是找到主線程都在干些啥玩意兒.我們知道一個(gè)線程的消息事件處理都是依賴于NSRunLoop來(lái)驅(qū)動(dòng),所以要知道線程正在調(diào)用什么方法,就需要從NSRunLoop來(lái)入手.CFRunLoop的代碼是開(kāi)源,可以在此處查閱到源代碼http://opensource.apple.com/source/CF/CF-1151.16/CFRunLoop.c,其中核心方法CFRunLoopRun簡(jiǎn)化后的主要邏輯大概是這樣的:


int32_t __CFRunLoopRun()

{//通知即將進(jìn)入runloop

__CFRunLoopDoObservers(KCFRunLoopEntry);

do

{

// 通知將要處理timer和source__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);? ? ? ? __CFRunLoopDoObservers(kCFRunLoopBeforeSources);? ? ? ? ? ? ? ? __CFRunLoopDoBlocks();//處理非延遲的主線程調(diào)用__CFRunLoopDoSource0();//處理UIEvent事件

//GCD dispatch main queueCheckIfExistMessagesInMainDispatchQueue();

// 即將進(jìn)入休眠_(dá)_CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);

// 等待內(nèi)核mach_msg事件mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();

// Zzz...

// 從等待中醒來(lái)__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

// 處理因timer的喚醒if(wakeUpPort == timerPort)? ? ? ? ? ? __CFRunLoopDoTimers();

// 處理異步方法喚醒,如dispatch_asyncelseif(wakeUpPort == mainDispatchQueuePort)? ? ? ? ? ? __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()

// UI刷新,動(dòng)畫(huà)顯示else__CFRunLoopDoSource1();

// 再次確保是否有同步的方法需要調(diào)用__CFRunLoopDoBlocks();

}while(!stop && !timeout);//通知即將退出runloop__CFRunLoopDoObservers(CFRunLoopExit);

}

不難發(fā)現(xiàn)NSRunLoop調(diào)用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之間,還有kCFRunLoopAfterWaiting之后,也就是如果我們發(fā)現(xiàn)這兩個(gè)時(shí)間內(nèi)耗時(shí)太長(zhǎng),那么就可以判定出此時(shí)主線程卡頓.

量化卡頓的程度

要監(jiān)控NSRunLoop的狀態(tài),我們需要使用到CFRunLoopObserverRef,通過(guò)它可以實(shí)時(shí)獲得這些狀態(tài)值的變化,具體的使用如下:

staticvoidrunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity,void*info)

{

MyClass *object = (__bridge MyClass*)info;

object->activity = activity;

}

- (void)registerObserver

{

CFRunLoopObserverContext context = {0,(__bridgevoid*)self,NULL,NULL};

CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,

kCFRunLoopAllActivities,

YES,

0,

&runLoopObserverCallBack,

&context);

CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

}

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

為了讓計(jì)算更精確,需要讓子線程更及時(shí)的獲知主線程N(yùn)SRunLoop狀態(tài)變化,所以dispatch_semaphore_t是個(gè)不錯(cuò)的選擇,另外卡頓需要覆蓋到多次連續(xù)小卡頓和單次長(zhǎng)時(shí)間卡頓兩種情景,所以判定條件也需要做適當(dāng)優(yōu)化.將上面兩個(gè)方法添加計(jì)算的邏輯如下:


staticvoidrunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity,void*info)

{

MyClass *object = (__bridge MyClass*)info;

// 記錄狀態(tài)值

object->activity = activity;

// 發(fā)送信號(hào)

dispatch_semaphore_t semaphore = moniotr->semaphore;

dispatch_semaphore_signal(semaphore);

}

- (void)registerObserver

{

CFRunLoopObserverContext context = {0,(__bridgevoid*)self,NULL,NULL};

CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,

kCFRunLoopAllActivities,

YES,

0,

&runLoopObserverCallBack,

&context);

CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

// 創(chuàng)建信號(hào)

semaphore = dispatch_semaphore_create(0);

// 在子線程監(jiān)控時(shí)長(zhǎng)

dispatch_async(dispatch_get_global_queue(0,0), ^{

while(YES)

{

// 假定連續(xù)5次超時(shí)50ms認(rèn)為卡頓(當(dāng)然也包含了單次超時(shí)250ms)

longst = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW,50*NSEC_PER_MSEC));

if(st !=0)

{

if(activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)

{

if(++timeoutCount <5)

continue;

NSLog(@"好像有點(diǎn)兒卡哦");

}

}

timeoutCount =0;

}

});

}

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

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

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);

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 前言 在移動(dòng)設(shè)備上開(kāi)發(fā)軟件,性能一直是我們最為關(guān)心的話題之一,我們作為程序員除了需要努力提高代碼質(zhì)量之外,及時(shí)發(fā)現(xiàn)...
    路飛_Luck閱讀 20,167評(píng)論 12 88
  • 首先如果遇到應(yīng)用卡頓或者因?yàn)閮?nèi)存占用過(guò)多時(shí)一般使用Instruments里的來(lái)進(jìn)行檢測(cè)。但對(duì)于復(fù)雜情況可能就需要用...
    攻克乃還_閱讀 1,900評(píng)論 0 7
  • 史上最全的iOS面試題及答案 iOS面試小貼士———————————————回答好下面的足夠了----------...
    Style_偉閱讀 2,372評(píng)論 0 35
  • 在Self2.0的這個(gè)項(xiàng)目的進(jìn)行中,和兩個(gè)朋友一起參加了一個(gè)閱讀活動(dòng)和一個(gè)15天的插畫(huà)課程,都是每天只要堅(jiān)持15分...
    奧利維亞的暢想生活閱讀 561評(píng)論 0 1
  • 回首我學(xué)習(xí)繪畫(huà)之路,小時(shí)候的我便對(duì)各種仕女圖、小萌物、美少女站非常癡迷,遇到不感興趣的課程時(shí),便偷偷地在筆記本...
    寧博Villa閱讀 915評(píng)論 16 17