iOS-instruments來檢驗?zāi)愕腶pp【轉(zhuǎn)】

“時間探測器”

天下武功,唯快不破。很多公司都信奉這個教條.恨不得把app壓法周期壓縮到最低,這就導(dǎo)致了開發(fā)中隱藏了很多問題,有點經(jīng)驗的工程師草率的優(yōu)化下,更糟的情況那些沒有經(jīng)驗的工程師甚至不會對app進行任何優(yōu)化.

某種程度上來說,你開發(fā)過程中是可以忽略性能優(yōu)化的. 十年前,移動設(shè)備的硬件資源是非常有限的.甚至連浮點數(shù)都是被禁止的.因為浮點數(shù)能導(dǎo)致代碼變大計算的速度變慢.

科技發(fā)展如此迅速的今天,硬件很大程度上可以彌補軟件的短板.現(xiàn)在的移動設(shè)備3D硬件處理的效率甚至媲美于PC機了,但是你不能總依賴于硬件和處理器速度來掩飾你APP做的多垃圾吧.(如果安卓系統(tǒng)跑在Iphone上還能夠像iOS一樣順滑嗎?其實是一個道理的)

性能這個概念很抽線,所以我們必須借助數(shù)據(jù)化圖形化的輸出方式.你可能花一周的時間去優(yōu)化一個有趣的算法,但是這算法只占總執(zhí)行時間的0.5%,不管你花多少精力去優(yōu)化它,沒人會注意到.相反一個for循環(huán)花費了90%的時間,你稍微修改下就能提高10%的效率,就是這個簡單的修改可以得到大家很大的好感.因為.他們運行app時的第一感受就是比之前快了很多.沒人會care你修改的是一個多牛逼的算法,還是一個簡單的for循環(huán).

這個說明了什么?

與其花費時間在優(yōu)化小細(xì)節(jié)上不如多點時間找到你改優(yōu)化的地方.

下面引出第一個工具 “時間事件查看器”(自己杜撰的名字英文—Time Profiler),———他可以測量時間的間隔,中斷程序執(zhí)行,跟蹤每個線程的堆棧.你可以想象下是xcode調(diào)試時按下暫停時的畫面


比如,100個樣本都在做1毫秒的間隔,然后在某個方法堆棧頂部有10個樣本,你可以推算出大概的時間有10%個10毫秒花費在此方法中,這是一個近似值.

廢話少說,時間是個檢測到的。

從xcode的菜單選擇Product-Profile,或者選擇?


程序會啟動Instruments,這時候你會看到一個選擇窗口


這是instruments所有測試儀器的面板,選擇 “timer profilter” 點擊“profile”回啟東模擬器和app,此時會要求你輸入一次密碼,以便instruments能有權(quán)限去截獲監(jiān)聽此進程。


在工具窗口中,可以看到時間計數(shù),并留下了一個小箭頭移動到右側(cè)的圖形在屏幕的中央上方。這表明該應(yīng)用程序正在運行。

現(xiàn)在開始運行app,搜索一些圖片,這時候你發(fā)現(xiàn)查找一個結(jié)果太慢了,而且搜索結(jié)果列表頁面滾動起來也是讓人無法忍受的。

首先,確保工具欄中的視圖選擇有選擇的所有三個選項,如下所示:


這將確保所有的面板都打開?,F(xiàn)在,研究下面的截圖和它下面的每個部分的解釋:


1. 錄控按鈕。中間的紅色按鈕將停止與啟動它被點擊時,應(yīng)用程序目前正在分析。注意這實際上是停止和啟動應(yīng)用程序,而不是暫停它。

2. 運行定時器和運行導(dǎo)航,定時器顯示APP已經(jīng)運行了多長時間,箭頭之間是可以移動的。如果停止,然后使用錄制按鈕重新啟動應(yīng)用程序,這將開始一個新的運行。顯示屏便會顯示“run2 of 2”,你可以回到第一次運行的數(shù)據(jù),首先你停止當(dāng)前運行,然后按下左箭頭回去。

3. 運行軌道。

4. 擴展面板,在時間探查儀器的情況下,它是用來跟蹤顯示堆棧。

5. 詳細(xì)地面板。它顯示了你正在使用的儀器的主要信息,這是使用頻率最高的部門,可以從它這里看到cpu運行的時間

6. 選項面板,稍后介紹。

重頭戲來了。

深究

執(zhí)行圖像搜索,并深究結(jié)果。我個人比較喜歡尋找“狗”,當(dāng)然你也可以選擇任意你想要的內(nèi)容。比如貓啊美女啊什么的。

現(xiàn)在上下滾動記下列表,讓時間探測器測量下數(shù)據(jù),然后注意看下屏幕的變化和數(shù)值。這些數(shù)值反應(yīng)了CPU周期。

但是你也許會發(fā)現(xiàn)下面的數(shù)值太多,看你的眼花繚亂。下面打開左邊的調(diào)用樹 然后按著如下的配置

以下介紹下配置選項:

Separate by Thread: 每個線程應(yīng)該分開考慮。只有這樣你才能揪出那些大量占用CPU的"重"線程

Invert Call Tree: 從上倒下跟蹤堆棧,這意味著你看到的表中的方法,將已從第0幀開始取樣,這通常你是想要的,只有這樣你才能看到CPU中話費時間最深的方法.也就是說FuncA{FunB{FunC}} 勾選此項后堆棧以C->B-A 把調(diào)用層級最深的C顯示在最外面

Hide Missing Symbols: 如果dSYM無法找到你的app或者系統(tǒng)框架的話,那么表中看不到方法名只能看到十六進制的數(shù)值,如果勾線此項可以隱藏這些符號,便于簡化數(shù)據(jù)

Hide System Libraries: 勾選此項你會顯示你app的代碼,這是非常有用的. 因為通常你只關(guān)心cpu花在自己代碼上的時間不是系統(tǒng)上的

Show Obj-C Only: 只顯示oc代碼 ,如果你的程序是像OpenGl這樣的程序,不要勾選側(cè)向因為他有可能是C++的

Flatten Recursion: 遞歸函數(shù), 每個堆棧跟蹤一個條目

Top Functions: 一個函數(shù)花費的時間直接在該函數(shù)中的總和,以及在函數(shù)調(diào)用該函數(shù)所花費的時間的總時間。因此,如果函數(shù)A調(diào)用B,那么A的時間報告在A花費的時間加上B.花費的時間,這非常真有用,因為它可以讓你每次下到調(diào)用堆棧時挑最大的時間數(shù)字,歸零在你最耗時的方法。

如果您已啟用上述選項,雖然有些值可能會略有不同,下面的結(jié)果的順序應(yīng)該是類似下表:


通過上面你能看到大部分時間都花在更新表格照片了.

雙擊此行,然后將會看到如下


那么這很有趣,不是嗎!幾乎四分之三的時間花費在setPhoto:方法都花在創(chuàng)造照片的圖像數(shù)據(jù)!

現(xiàn)在可以看到的是什么問題,NSData’s dataWithContentsOfURL 方法并不會立即返回,因為要從網(wǎng)上去數(shù)據(jù),每次調(diào)用都需要長達(dá)幾秒的時間返回,而此方法運行在主線程,可想而知會有什么結(jié)果了.

其實為了解決這個問題,類提供了一個ImageCache 的后臺異步下載的方法.

現(xiàn)在,您可以切換到Xcode和手動找到該文件,但儀器有一個方便的“打開Xcode中”按鈕,就在你的眼前。找到它的面板只是上面的代碼并單擊它:


想如下修改

-?(void)setPhoto:(FlickrPhoto?*)photo?{

_photo?=?photo;

self.textLabel.text?=?photo.title;f

//????NSData?*imageData?=?[NSData?dataWithContentsOfURL:_photo.thumbnailUrl];

//????self.imageView.image?=?[UIImage?imageWithData:imageData];

[[ImageCache?sharedInstance]?downloadImageAtURL:_photo.thumbnailUrl

completionHandler:^(UIImage?*image)?{

self.imageView.image?=?image;

[self?setNeedsLayout];

}];

}

修改好厚,在儀器重新運行該應(yīng)用程序Product—Profile(或cmd-記住,這些快捷鍵真的會為您節(jié)省一些時間)。

請注意,這個時候會再問一次你是否使用一起。這是因為你還有一個窗口中打開這個程序,及儀器假定您要使用相同的選項再次運行。

執(zhí)行一些更多的搜索,并注意此時用戶界面不是那么卡頓了!這些圖像現(xiàn)在異步加載,并緩存在后臺,所以一旦他們已經(jīng)被下載一次,他們不必再次下載。

看上去很不錯!是時候發(fā)布了嗎? 當(dāng)然還不夠

分配,分配,分配

接下來的儀器是分配工具。它能給出你所有創(chuàng)建和存儲它們的內(nèi)存的詳細(xì)信息,它也顯示你保留了每個對象的計數(shù)。

關(guān)閉儀器,回到Xcode和選擇Product->Profile。然后,從選擇器分配并單擊配置文件。如下圖:


程序再次打開 然后你會看到


這個時候你會發(fā)現(xiàn)兩個曲目。一個叫(分配)Allocations,以及一個被稱為VM Tracker(虛擬機跟蹤)。該分配軌道將詳細(xì)在本教程中討論;虛擬機跟蹤也是非常有用的,但更復(fù)雜一點。

所以你的錯誤會追蹤下?

有隱藏的項目,你可能不知道有東西在那兒。你可能已經(jīng)聽說了內(nèi)存泄漏。但你可能不知道的是,其實有兩種泄漏。

第一個是真正的內(nèi)存泄漏,一個對象尚未被釋放,但是不再被引用的了。因此,存儲器不能被重新使用。

第二類泄漏是比較麻煩一些。這就是所謂的“無界內(nèi)存增長”。這發(fā)生在內(nèi)存繼續(xù)分配,并永遠(yuǎn)不會有機會被釋放。

如果永遠(yuǎn)這樣下去你的程序占用的內(nèi)存會無限大,當(dāng)超過一定內(nèi)存的話 會被系統(tǒng)的看門狗給kill掉.

建立一個場景,你可以檢測出無限的內(nèi)存增長。首先,在應(yīng)用程序使10個不同的搜索(不要用已經(jīng)存在的搜索)。確保搜索的一些結(jié)果!現(xiàn)在讓程序等待幾秒鐘。

你應(yīng)該已經(jīng)注意到,在分配的軌道圖不斷上升。這是告訴你的,內(nèi)存被分配了。它的這一特征,將引導(dǎo)你找到無限的內(nèi)存增長。

你將要執(zhí)行的是“heap shot analysis”。為此,按這個按鈕叫“Mark Heap”。你會發(fā)現(xiàn)的詳細(xì)面板左側(cè)的按鈕


按下它,你會看到一個紅色的標(biāo)志出現(xiàn)在軌道上,像這樣:


heap shot分析的目的是執(zhí)行一個動作多次,看看如果內(nèi)存是否無限增長。搜索一個內(nèi)容,稍等幾秒加載圖像,然后返回主頁。然后再標(biāo)記堆。反復(fù)這樣做不同的搜索。

演戲幾個搜索后,儀器會看起來像這樣:


這時你應(yīng)該會疑問。圖中的藍(lán)色是怎么回事了,你繼續(xù)這樣操作10次這樣的搜索 藍(lán)色還不斷變高:

那肯定是不好的。別急,有什么關(guān)于內(nèi)存的警告?你知道這些,對不對?內(nèi)存警告是告訴一個應(yīng)用程序,內(nèi)存警告是ios處理app最好的方式尤其是在內(nèi)存越來越吃緊的時候,你需要清除一些內(nèi)存。

內(nèi)存一直增長其實也不一定是你的代碼除了問題,也有可能是UIKit 系統(tǒng)框架本身導(dǎo)致的.

通過選擇HardwareSimulate內(nèi)存警告在iOS模擬器的菜單欄模擬內(nèi)存警告。你會發(fā)現(xiàn),記憶體使用量出現(xiàn)小幅回落,但絕對不會回到它應(yīng)該的。所以還是有無限的內(nèi)存增長發(fā)生的地方。

究其原因,堆出手做鉆進搜索的每次迭代后,你可以看到內(nèi)存的分配每個鏡頭之間。一起來看看在詳細(xì)信息面板,你會看到一堆一堆的鏡頭。

在iOS模擬器的菜單欄中選擇hardwaresimulate內(nèi)存警告模擬內(nèi)存警告。你會發(fā)現(xiàn)內(nèi)存使用會出現(xiàn)小幅回落,但肯定不會回到它應(yīng)該在的地方。

每一次的搜索后做你可以看到內(nèi)存已拍攝之間的分配。在詳細(xì)信息面板看一看,你會看到一好多堆鏡頭。

穩(wěn)準(zhǔn)狠

第一個堆鏡頭作為參照,然后隨便打開一個堆鏡頭,你會看到如下:


靠,這是一個很大的對象!從哪里開始看呢?

最好的方式是通過列表,你在你的應(yīng)用程序直接使用的類。在這種情況下,HTTPHeaderDict,CGRegion,CGPath,CFNumber,等等都是可以忽略了。

但是,一個突出的是UIImage,這肯定是在你程序使用的。點擊上的UIImage左側(cè)的箭頭顯示的完整列表。選擇一個,在擴展詳細(xì)信息面板:


圖中灰色的是系統(tǒng)庫,黑色部分是你應(yīng)用的代碼,要獲得此跟蹤更多的上下文,雙擊唯一的黑框ImageCache方法,這時候?qū)⒌艮D(zhuǎn)到如下方法

-?(void)downloadImageAtURL:(NSURL*)url?completionHandler:(ImageCacheDownloadCompletionHandler)completion?{

UIImage?*cachedImage?=?[self?imageForKey:[url?absoluteString]];

if(cachedImage)?{

completion(cachedImage);

}else{

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{

NSData?*data?=?[NSData?dataWithContentsOfURL:url];

UIImage?*image?=?[UIImage?imageWithData:data];

[self?setImage:image?forKey:[url?absoluteString]];

dispatch_async(dispatch_get_main_queue(),?^{

completion(image);

});

});

}

}

工具是非常有用的,你現(xiàn)在要努力通過自己的代碼思考發(fā)生了什么.看看通過上面的方法,你會看到它調(diào)用一個名為setImage方法:forKey:。這種方法在緩存以防它再次使用以后的應(yīng)用程序的圖像。啊!那么這肯定聽起來像它可能是一個問題!

一起來看看該方法的實現(xiàn):

-?(void)setImage:(UIImage*)image?forKey:(NSString*)key?{

[_cache?setObject:image?forKey:key];

}

從網(wǎng)絡(luò)上下載一個圖片添加字典中,你會注意到這些圖片從來沒有從字典清楚過。

,這就是內(nèi)存為什么會一直增長,因為應(yīng)用程序并不會從緩存里刪除東西.它只會一直增加他們。

要解決此問題,你需要的是ImageCache收到UIApplication內(nèi)存吃緊的警告時.清理緩存。

為了使ImageCache能接收通知,修改init方法如下:

-?(id)init?{

if((self?=?[superinit]))?{

_cache?=?[NSMutableDictionarynew];

[[NSNotificationCenter?defaultCenter]?addObserver:self?selector:@selector(memoryWarning:)?name:UIApplicationDidReceiveMemoryWarningNotification?object:nil];

}

returnself;

}

注冊UIApplicationDidReceiveMemoryWarningNotification執(zhí)行memoryWarning:方法。

-?(void)memoryWarning:(NSNotification*)note?{

[_cache?removeAllObjects];

}

memoryWarning刪除緩存中的所有對象。這將確保沒有持圖像。

為了測試此修復(fù)程序,再次啟動儀器(從Xcode中有cmd)和重復(fù)的步驟。不要忘了在模擬結(jié)束內(nèi)存警告!

注意:請確保您從Xcode中退出,重新構(gòu)建,而不是僅僅點擊儀器儀表上的紅色按鈕,以確保您使用的是最新的代碼。

這一次分析應(yīng)該是這樣的:


這個時候,內(nèi)存受到內(nèi)存警告后急劇下降。但還是有一些內(nèi)存整體增長,但遠(yuǎn)不及像以前那樣。

究其原因還是有一定的增長確實是由于系統(tǒng)庫,并沒有太多可以做的??磥恚到y(tǒng)庫不釋放所有的內(nèi)存,這可能是由設(shè)計或可能是一個錯誤。你可以在你的應(yīng)用程序做的是釋放盡可能多的內(nèi)存越好,你已經(jīng)做到這一點!

干得好!還有一個問題,修補了, - 仍然有泄漏,你還沒有解決的第一種類型的問題。

內(nèi)存泄露

內(nèi)存泄漏的儀器。這是用來找到第一類泄漏前面提到的 - 當(dāng)一個對象不再被引用時出現(xiàn)的那種。

檢測泄漏是可以理解的一個很復(fù)雜的事情,但泄漏的工具記得,已分配的所有對象,并定期通過掃描每個對象以確定是否有任何不能從任何其他對象訪問的。

關(guān)閉儀器,回到Xcode和選擇Product->Profile


回到你的應(yīng)用程序!執(zhí)行搜索,得到結(jié)果。然后點選結(jié)果的預(yù)覽行打開全屏瀏覽器。按下旋轉(zhuǎn)按鈕在左上角,然后再按一次。

回到儀器,等待片刻。如果你已經(jīng)正確地完成上述步驟后,你會發(fā)現(xiàn)泄漏已經(jīng)出現(xiàn)了!你的工具窗口將看起來像這樣:


返回到模擬器,并按下旋轉(zhuǎn)幾次。然后返回到儀器和等會,得到如下結(jié)果:


哪來的泄漏從哪里來?擴展詳細(xì)信息面板


在擴展的詳細(xì)信息面板打開CGContext上名單。在列表中選擇CGContext上的元素之一,這表明導(dǎo)致要創(chuàng)建的對象,如下面的堆棧跟蹤:


再次,涉及到你的代碼中的幀顯示為黑色。由于只有一個,雙擊它,看看代碼的方法。

有問題的方法是rotateTapped: ,這是被調(diào)用時被竊聽旋轉(zhuǎn)按鈕的處理程序。這種方法旋轉(zhuǎn)原始圖像,并創(chuàng)建一個新的圖像,如下:

-?(void)rotateTapped:(id)sender?{

UIImage?*currentImage?=?_imageView.image;

CGImageRef?currentCGImage?=?currentImage.CGImage;

CGSize?originalSize?=?currentImage.size;

CGSize?rotatedSize?=?CGSizeMake(originalSize.height,?originalSize.width);

CGContextRef?context?=?CGBitmapContextCreate(NULL,

rotatedSize.width,

rotatedSize.height,

CGImageGetBitsPerComponent(currentCGImage),

CGImageGetBitsPerPixel(currentCGImage)?*?rotatedSize.width,

CGImageGetColorSpace(currentCGImage),

CGImageGetBitmapInfo(currentCGImage));

CGContextTranslateCTM(context,?rotatedSize.width,?0.0f);

CGContextRotateCTM(context,?M_PI_2);

CGContextDrawImage(context,?(CGRect){.origin=CGPointZero,?.size=originalSize},?currentCGImage);

CGImageRef?newCGImage?=?CGBitmapContextCreateImage(context);

UIImage?*newImage?=?[UIImage?imageWithCGImage:newCGImage];

self.imageView.image?=?newImage;

}

再次,儀器只能在這里給你一個提示,問題出在哪里,它不能告訴你確切位置的泄漏。這是唯一能夠證明你在創(chuàng)建對象泄露的地方.你可能認(rèn)為ARC并有不可能是造成代碼中內(nèi)存泄漏…對不對?

回想一下,ARC只涉及Objective-C的對象。它不管理保留和的CoreFoundation對象而不是Objective-C的對象的釋放。

啊,現(xiàn)在它開始變得明顯的問題是什么?

-CGContextRef和CGImageRef對象永遠(yuǎn)不會被釋放!為了解決這個問題,在rotateTapped方法的末尾添加以下兩行代碼:

CGImageRelease(newCGImage);

CGContextRelease(context);

這兩種調(diào)用都需要來維護這兩個對象的保留計數(shù)。這個說明,你還需要了解引用計數(shù) - 即使你在你的項目中使用的ARC!

從在Xcode中,使用cmd工具構(gòu)建和運行應(yīng)用程序。

在使用泄漏儀器儀器再看看應(yīng)用程序,看看是否泄漏的被固定。如果你正確地遵循上述步驟,泄漏應(yīng)消失!

原文:http://www.cocoachina.com/industry/20140114/7696.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容