ios系類教程之用instruments來檢驗你的app

來源:互聯網
上載者:User

標籤:

ios系類教程之用instruments來檢驗你的app
 

為了節省大家的時間,提供一個示範的Demo給大家.代碼傳送門.
下載後解壓然後用xcode開啟.
編譯運行APP後 然後在搜尋方塊內輸入任意詞彙,點擊結果你會看到下面的結果

正如你所見的,這個app很簡單.程式其實調用的是Flickr的API,通過app頂部的搜尋方塊執行搜尋後在下面的tableview顯示你搜尋的搜尋字詞,搜尋字詞後面的括弧內有搜尋結果的個數,點擊此行進入一個略所圖的結果清單頁面 如. 點擊其中一行 進入映像的大圖模式,在這個頁面你可以根據需要旋轉映像.
到目前為止頁面看起來差不多了,你也許會想應該可以直接提交appstore了吧.接下來這篇文章將會教你instruments工具來提高你app效能和穩定性.

“時間探測器”

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

某種程度上來說,你開發過程中是可以忽略效能最佳化的. 十年前,行動裝置的硬體資源是非常有限的.甚至連浮點數都是被禁止的.因為浮點數能導致代碼變大計算的速度變慢.
科技發展如此迅速的今天,硬體很大程度上可以彌補軟體的短板.現在的行動裝置3D硬體處理的效率甚至媲美於PC機了,但是你不能總依賴於硬體和處理器速度來掩飾你APP做的多垃圾吧.(如果安卓系統跑在Iphone上還能夠像IOS一樣順滑嗎?,其實是一個道理的)

效能這個概念很抽線,所以我們必須藉助資料化圖形化的輸出方式.你可能花一周的時間去最佳化一個有趣的演算法,但是這演算法只佔總執行時間的0.5%,不管你花多少精力去最佳化它,沒人會注意到.相反一個for迴圈花費了90%的時間,你稍微修改下就能提高10%的效率,就是這個簡單的修改可以得到大家很大的好感.因為.他們運行app時的第一感受就是比之前快了很多.沒人會care你修改的是一個多牛逼的演算法,還是一個簡單的for迴圈.
這個說明了什麼.
與其花費時間在最佳化小細節上不如多點時間找到你改最佳化的地方.

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

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

廢話少說,時間是個檢測到的.
從xcode的菜單選擇Product-Profile,或者選擇?I,
程式會啟動Instruments,這時候你會看到一個選擇視窗

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

在工具視窗中,可以看到時間計數,並留下了一個小箭頭移動到右側的圖形在螢幕的中央上方。這表明該應用程式正在運行。

現在開始運行app,搜尋一些圖片,這時候你發現尋找一個結果太慢了,而且搜尋結果列表頁面滾動起來也是讓人無法忍受的,
首先,確保工具列中的視圖選擇有選擇的所有三個選項,如下所示:

?這將確保所有的面板都開啟。現在,研究下面的和它下面的每個部分的解釋:

1. 錄控按鈕。中間的紅色按鈕將停止與啟動它被點擊時,應用程式目前正在分析。注意這實際上是停止和啟動應用程式,而不是暫停它。  2. 運行定時器和運行導航,定時器顯示APP已經運行了多長時間,箭頭之間是可以移動的。如果停止,然後使用錄製按鈕重新啟動應用程式,這將開始一個新的運行。顯示屏便會顯示“run2 of 2”,你可以回到第一次啟動並執行資料,首先你停止當前運行,然後按下左箭頭回去。  3. 運行軌道.   4. 擴充面板,在時間探查儀器的情況下,它是用來跟蹤顯示堆棧  5. 詳細地面板。它顯示了你正在使用的儀器的主要資訊,這是使用頻率最高的部門,可以從它這裡看到cpu啟動並執行時間   6. 選項面板 稍後介紹

重頭戲來了.

深究

執行Image Search,並深究結果。我個人比較喜歡尋找“狗”,當然你也可以選擇任意你想要的內容.比如貓啊美女啊什麼的.

現在上下滾動記下列表,讓時間探測器測量下資料,然後注意看下螢幕的變化和數值.這些數值反應了CPU周期.

但是你也許會發現下面的數值太多,看你的眼花繚亂. 下面開啟左邊的調用樹 然後按著如下的配置

以下介紹下配置選項:

Separate by Thread: 每個線程應該分開考慮。只有這樣你才能揪出那些大量佔用CPU的重線程  Invert Call Tree: 從上倒下跟蹤堆棧,這意味著你看到的表中的方法,將已從第0幀開始取樣,這通常你是想要的,只有這樣你才能看到CPU中話費時間最深的方法.也就是說FuncA{FunB{FunC}} 勾選此項後堆棧以C->B-A 把調用層級最深的C顯示在最外面 Hide Missing Symbols: 如果dSYM無法找到你的app或者系統架構的話,那麼表中看不到方法名只能看到十六進位的數值,如果勾線此項可以隱藏這些符號,便於簡化資料Hide System Libraries: 勾選此項你會顯示你app的代碼,這是非常有用的. 因為通常你只關心cpu花在自己代碼上的時間不是系統上的Show Obj-C Only: 只顯示oc代碼 ,如果你的程式是像OpenGl這樣的程式,不要勾選側向因為他有可能是C++的  Flatten Recursion: 遞迴函式, 每個堆疊追蹤一個條目Top Functions: 一個函數花費的時間直接在該函數中的總和,以及在函數調用該函數所花費的時間的總時間。因此,如果函數A調用B,那麼A的時間報告在A花費的時間加上B.花費的時間,這非常真有用,因為它可以讓你每次下到呼叫堆疊時挑最大的時間數字,歸零在你最耗時的方法。

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

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

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

那麼這很有趣,不是嗎!幾乎四分之三的時間花費在setPhoto:方法都花在創造照片的映像資料!
現在可以看到的是什麼問題,NSData’s dataWithContentsOfURL 方法並不會立即返回,因為要從網上去資料,每次調用都需要長達幾秒的時間返回,而此方法運行在主線程,可想而知會有什麼結果了.
其實為瞭解決這個問題,類提供了一個ImageCache 的後台非同步下載的方法.

現在,您可以切換到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];                                  }];}

修改好厚,在儀器重新運行該應用程式Product—Profile(或?I-記住,這些快速鍵真的會為您節省一些時間)。
請注意,這個時候會再問一次你是否使用一起。這是因為你還有一個視窗中開啟這個程式,及儀器假定您要使用相同的選項再次運行。
執行一些更多的搜尋,並注意此時使用者介面不是那麼卡頓了!這些映像現在非同步載入,並緩衝在後台,所以一旦他們已經被下載一次,他們不必再次下載。
看上去很不錯!是時候發布了嗎? 當然還不夠

分配,分配,分配

接下來的儀器是分配工具。它能給出你所有建立和儲存它們的記憶體的詳細資料,它也顯示你保留了每個對象的計數。

關閉儀器,回到Xcode和選擇Product->Profile。然後,從選取器分配並單擊設定檔。如:

程式再次開啟 然後你會看到

這個時候你會發現兩個曲目。一個叫(分配)Allocations,以及一個被稱為VM Tracker(虛擬機器跟蹤)。該分配軌道將詳細在本教程中討論;虛擬機器跟蹤也是非常有用的,但更複雜一點。
所以你的錯誤會追蹤下?
有隱藏的項目,你可能不知道有東西在那兒。你可能已經聽說了記憶體流失。但你可能不知道的是,其實有兩種泄漏。
第一個是真正的記憶體流失,一個對象尚未被釋放,但是不再被引用的了。因此,儲存空間不能被重新使用。
第二類泄漏是比較麻煩一些。這就是所謂的“無界記憶體增長”。這發生在記憶體繼續分配,並永遠不會有機會被釋放。
如果永遠這樣下去你的程式佔用的記憶體會無限大,當超過一定記憶體的話 會被系統的看門狗給kill掉.

建立一個情境,你可以檢測出無限的記憶體增長。首先,在應用程式使10個不同的搜尋(不要用已經存在的搜尋)。確保搜尋的一些結果!現在讓程式等待幾秒鐘。

你應該已經注意到,在分配的軌道圖不斷上升。這是告訴你的,記憶體被分配了。它的這一特徵,將引導你找到無限的記憶體增長。
你將要執行的是“heap shot analysis”。為此,按這個按鈕叫“Mark Heap”。你會發現的詳細面板左側的按鈕

按下它,你會看到一個紅色的標誌出現在軌道上,像這樣:

heap shot分析的目的是執行一個動作多次,看看如果記憶體是否無限增長。搜尋一個內容,稍等幾秒載入映像,然後返回首頁。然後再標記堆。反覆這樣做不同的搜尋。
演戲幾個搜尋後,儀器會看起來像這樣:

這時你應該會疑問。圖中的藍色是怎麼回事了,你繼續這樣操作10次這樣的搜尋 藍色還不斷變高:
那肯定是不好的。別急,有什麼關於記憶體的警告?你知道這些,對不對?記憶體警告是告訴一個應用程式,記憶體警告是ios處理app最好的方式尤其是在記憶體越來越吃緊的時候,你需要清除一些記憶體。
記憶體一直增長其實也不一定是你的代碼除了問題,也有可能是UIKit 系統架構本身導致的.

通過選擇HardwareSimulate記憶體警告在iOS模擬器的功能表列類比記憶體警告。你會發現,記憶體使用量出現小幅回落,但絕對不會回到它應該的。所以還是有無限的記憶體增長發生的地方。
究其原因,堆出手做鑽進搜尋的每次迭代後,你可以看到記憶體的分配每個鏡頭之間。一起來看看在詳細資料面板,你會看到一堆一堆的鏡頭。

在iOS模擬器的功能表列中選擇hardwaresimulate記憶體警告類比記憶體警告。你會發現記憶體使用量會出現小幅回落,但肯定不會回到它應該在的地方。
每一次的搜尋後做你可以看到記憶體已拍攝之間的分配。在詳細資料面板看一看,你會看到一好多堆鏡頭。

穩准狠

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

靠,這是一個很大的對象!從哪裡開始看呢?
最好的方式是通過列表,你在你的應用程式直接使用的類。在這種情況下,HTTPHeaderDict,CGRegion,CGPath,CFNumber,等等都是可以忽略了。
但是,一個突出的是UIImage,這肯定是在你程式使用的。點擊上的UIImage左側的箭頭顯示的完整列表。選擇一個,在擴充詳細資料面板:

圖中灰色的是系統庫,黑色部分是你應用的代碼,要獲得此跟蹤更多的上下文,雙擊唯一的黑框ImageCache方法,這時候將掉轉到如下方法

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

工具是非常有用的,你現在要努力通過自己的代碼思考發生了什麼.看看通過上面的方法,你會看到它調用一個名為setImage方法:forKey:。這種方法在緩衝以防它再次使用以後的應用程式的映像。啊!那麼這肯定聽起來像它可能是一個問題!
一起來看看該方法的實現:

- (void)setImage:(UIImage*)image forKey:(NSString*)key {    [_cache setObject:image forKey:key];}

從網路上下載一個圖片添加字典中,你會注意到這些圖片從來沒有從字典清楚過
,這就是記憶體為什麼會一直增長,因為應用程式並不會從緩衝裡刪除東西.它只會一直增加他們.
要解決此問題,你需要的是ImageCache收到UIApplication記憶體吃緊的警告時.清理緩衝.

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

- (id)init {    if ((self = [super init])) {        _cache = [NSMutableDictionary new];        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(memoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];    }    return self;}

註冊UIApplicationDidReceiveMemoryWarningNotification執行memoryWarning:方法。

- (void)memoryWarning:(NSNotification*)note {    [_cache removeAllObjects];}

memoryWarning刪除緩衝中的所有對象。這將確保沒有持映像。
為了測試此修複程式,再次啟動儀器(從Xcode中有?I)和重複的步驟。不要忘了在類比結束記憶體警告!

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

這一次分析應該是這樣的:

這個時候,記憶體受到記憶體警告後急劇下降。但還是有一些記憶體整體增長,但遠不及像以前那樣。
究其原因還是有一定的增長確實是由於系統庫,並沒有太多可以做的。看來,系統庫不釋放所有的記憶體,這可能是由設計或可能是一個錯誤。你可以在你的應用程式做的是釋放儘可能多的記憶體越好,你已經做到這一點!
幹得好!還有一個問題,修補了, - 仍然有泄漏,你還沒有解決的第一種類型的問題。

記憶體泄露

記憶體流失的儀器。這是用來找到第一類泄漏前面提到的 - 當一個對象不再被引用時出現的那種
檢測泄漏是可以理解的一個很複雜的事情,但泄漏的工具記得,已指派的所有對象,並定期通過掃描每個對象以確定是否有任何不能從任何其他對象訪問的。
關閉儀器,回到Xcode和選擇Product->Profile

回到你的應用程式!執行搜尋,得到結果。然後點選結果的預覽行開啟全屏瀏覽器。按下旋轉按鈕在左上方,然後再按一次。
回到儀器,等待片刻。如果你已經正確地完成上述步驟後,你會發現泄漏已經出現了!你的工具視窗將看起來像這樣:

返回到模擬器,並按下旋轉幾次。然後返回到儀器和等會,得到如下結果:

哪來的泄漏從哪裡來?擴充詳細資料面板

在擴充的詳細資料面板開啟CGContext上名單。在列表中選擇CGContext上的元素之一,這表明導致要建立的對象,如下面的堆疊追蹤:

再次,涉及到你的代碼中的幀顯示為黑色。由於只有一個,雙擊它,看看代碼的方法。
有問題的方法是rotateTapped: ,這是被調用時被竊聽旋轉按鈕的處理常式。這種方法旋轉原始映像,並建立一個新的映像,如下:

- (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;}

再次,儀器只能在這裡給你一個提示,問題出在哪裡,它不能告訴你確切位置的泄漏。這是唯一能夠證明你在建立對象泄露的地方.你可能認為ARC並有不可能是造成代碼中記憶體流失…對不對?
回想一下,ARC只涉及Objective-C的對象。它不管理保留和的CoreFoundation對象而不是Objective-C的對象的釋放。
啊,現在它開始變得明顯的問題是什麼
-CGContextRef和CGImageRef對象永遠不會被釋放!為瞭解決這個問題,在rotateTapped方法的末尾添加以下兩行代碼:

CGImageRelease(newCGImage);CGContextRelease(context);

這兩種調用都需要來維護這兩個對象的保留計數。這個說明,你還需要瞭解引用計數 - 即使你在你的項目中使用的ARC!
從在Xcode中,使用?I工具構建和運行應用程式。
在使用泄漏儀器儀器再看看應用程式,看看是否泄漏的被固定。如果你正確地遵循上述步驟,泄漏應消失!

ios系類教程之用instruments來檢驗你的app

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.