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

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

在移動(dòng)設(shè)備上開發(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è)陂_發(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的代碼是開源,可以在此處查閱到源代碼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)畫顯示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);

}

只需要另外再開啟一個(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)系作者
  • 序言:七十年代末享甸,一起剝皮案震驚了整個(gè)濱河市畅哑,隨后出現(xiàn)的幾起案子汹胃,更是在濱河造成了極大的恐慌勋陪,老刑警劉巖揩瞪,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拧略,居然都是意外死亡芦岂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門垫蛆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)禽最,“玉大人,你說(shuō)我怎么就攤上這事袱饭〈ㄎ蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵虑乖,是天一觀的道長(zhǎng)懦趋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)疹味,這世上最難降的妖魔是什么仅叫? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任帜篇,我火速辦了婚禮,結(jié)果婚禮上诫咱,老公的妹妹穿的比我還像新娘笙隙。我一直安慰自己,他們只是感情好坎缭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布竟痰。 她就那樣靜靜地躺著,像睡著了一般幻锁。 火紅的嫁衣襯著肌膚如雪凯亮。 梳的紋絲不亂的頭發(fā)上边臼,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天哄尔,我揣著相機(jī)與錄音,去河邊找鬼柠并。 笑死岭接,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臼予。 我是一名探鬼主播鸣戴,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼粘拾!你這毒婦竟也來(lái)了窄锅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缰雇,失蹤者是張志新(化名)和其女友劉穎入偷,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體械哟,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疏之,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暇咆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锋爪。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖爸业,靈堂內(nèi)的尸體忽然破棺而出其骄,到底是詐尸還是另有隱情,我是刑警寧澤扯旷,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布拯爽,位于F島的核電站,受9級(jí)特大地震影響薄霜,放射性物質(zhì)發(fā)生泄漏某抓。R本人自食惡果不足惜纸兔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望否副。 院中可真熱鬧汉矿,春花似錦、人聲如沸备禀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)曲尸。三九已至赋续,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間另患,已是汗流浹背纽乱。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昆箕,地道東北人鸦列。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鹏倘,于是被迫代替她去往敵國(guó)和親薯嗤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

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