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

淺談iOS Crash(二)

移動開發(fā) iOS
僵尸對象是已經(jīng)被釋放掉的對象。一般來說,訪問已經(jīng)釋放的對象或向它發(fā)消息會引起錯誤。因為指針指向的內(nèi)存塊認(rèn)為你無權(quán)訪問或它無法執(zhí)行該消息,這時候內(nèi)核會拋出一個異常( EXC ),表明你不能訪問該存儲區(qū)域(BAD ACCESS)。

淺談iOS Crash(一)

一、僵尸對象(Zombie Objects)

1、概述

  • 僵尸對象:已經(jīng)被釋放掉的對象。一般來說,訪問已經(jīng)釋放的對象或向它發(fā)消息會引起錯誤。因為指針指向的內(nèi)存塊認(rèn)為你無權(quán)訪問或它無法執(zhí)行該消息,這時候內(nèi)核會拋出一個異常( EXC ),表明你不能訪問該存儲區(qū)域(BAD ACCESS)。(EXC_BAD_ACCESS類型錯誤)
  • 調(diào)試解決該類問題一般采用NSZombieEnabled(開啟僵尸模式)。

2、使用NSZombieEnabled

  • Xcode提供的NSZombieEnabled,通過生成僵尸對象來替換dealloc的實現(xiàn),當(dāng)對象引用計數(shù)為0的時候,將需要dealloc的對象轉(zhuǎn)化為僵尸對象。如果之后再給這個僵尸對象發(fā)消息,則拋出異常。先選中Product -> Scheme -> Edit Scheme -> Diagnostics -> 勾選Zombie Objects 項,顯示如下:

 

設(shè)置NSZombieEnabled.png

  • 然后在Product -> Scheme -> Edit Scheme -> Arguments設(shè)置NSZombieEnabled、MallocStackLoggingNoCompact兩個變量,且值均為YES。顯示如下: 

 

設(shè)置NSZombieEnabled和MallocStackLoggingNoCompact.png

  • 僅設(shè)置Zombie Objects的話,如果Crash發(fā)生在當(dāng)前調(diào)用棧,系統(tǒng)可以把崩潰原因定位到具體代碼中;但是如果Crash不是發(fā)生在當(dāng)前調(diào)用棧,系統(tǒng)僅僅告知崩潰地址,所以我們需要添加變量MallocStackLoggingNoCompact,讓Xcode記錄每個地址alloc的歷史,然后通過命令將地址還原出來。
  • Xcode 6之前還可以使用gdb,可以使用info malloc-history address命令來將發(fā)生崩潰的地址還原成具體的代碼行,Xcode 7之后只能使用lldb,使用命令bt來打印調(diào)用堆棧。下面是某Crash通過僵尸模式調(diào)試,使用bt查看的效果。

 

bt效果.png

說明:發(fā)版前要將僵尸對象檢測這些設(shè)置都去掉,否則每次通過指針訪問對象時,都去檢查指針指向的對象是否為僵尸對象,這就影響效率了。

3、代碼中的注意事項

在ARC時代,避免訪問釋放掉的內(nèi)存,代碼需要注意的地方有:

  • 檢查代碼1 :不能使用assgin或 unsafe_unretained修飾指向OC對象的指針

assgin和unsafe_unretained表示不持對象,是弱引用。如果指針指向的對象被釋放了,它們就變成了野指針,很有可能發(fā)生Crash。

建議1: assign僅用于修飾NSInteger等OC基礎(chǔ)類型,以及short、int、double、結(jié)構(gòu)體等C數(shù)據(jù)類型,不修飾對象指針;

建議2: OC對象屬性一般使用strong關(guān)鍵字(默認(rèn))修飾。

建議3: 如果需要弱引用OC對象,建議使用weak關(guān)鍵字,因為被weak指針?biāo)玫膶ο蟊换厥蘸?,weak指針會被賦為nil(空指針),給nil發(fā)任何消息都不會出問題。使用weak修飾代理對象屬性就是很好的例子。

  • 檢查代碼2 :Core Foundation等底層操作

Core Foundation等底層操作它們不支持ARC,還需要手動內(nèi)存管理。

建議: 注意CF對象的創(chuàng)建和釋放。

二、野指針(Wild pointer)

1、概述

  • 野指針是指向一個已刪除的對象 或 未申請訪問受限內(nèi)存區(qū)域的指針。而這里的野指針主要是對象釋放后,指針未置空導(dǎo)致的野指針。該類Crash發(fā)生比較隨機(jī),找出來比較費(fèi)勁,比較常見的做法是,在開發(fā)階段,提高這類Crash的復(fù)現(xiàn)率,盡可能得將其發(fā)現(xiàn)并解決。
  • 向OC對象發(fā)出release消息,只是標(biāo)記對象占用的那塊內(nèi)存可以被釋放,系統(tǒng)并沒有立即收回內(nèi)存;如果此時還向該對象發(fā)送其他消息,可能會發(fā)生Crash,也可能沒有問題。下圖是 訪問野指針(指向已刪除對象的指針)可能發(fā)生的情況。

 

訪問野指針可能發(fā)生的情況圖.png

  • 從上圖可以知道,野指針造成的Crash的隨機(jī)性比較大,但是被隨機(jī)填入的數(shù)據(jù)是不可訪問的情況下,Crash是必現(xiàn)的。我們的思路是:想辦法給 野指針指向的內(nèi)存填寫不可訪問的數(shù)據(jù),讓隨機(jī)的Crash變成必現(xiàn)的Crash。

2、設(shè)置Malloc Scribble

Xcode提供的Malloc Scribble,可以將對象釋放后在內(nèi)存上填上不可訪問的數(shù)據(jù),將隨機(jī)發(fā)生變成不隨機(jī)發(fā)生的事情,選中Product->Scheme->Edit Scheme ->Diagnostics – >勾選 Malloc Scribble項,結(jié)果如下:

 

設(shè)置Malloc Scribble.png

設(shè)置了Enable Scribble,在對象申請內(nèi)存后在申請的內(nèi)存上填0xaa,內(nèi)存釋放后在釋放的內(nèi)存上填0x55;如果內(nèi)存未被初始化就被訪問,或者釋放后被訪問,Crash必現(xiàn)。

說明:該方法必須連接Xcode運(yùn)行代碼才發(fā)現(xiàn),不適合測試人員使用??梢曰趂ishhook ,選擇hook對象釋放的接口(C的free函數(shù)),達(dá)到和設(shè)置Enable Scribble一樣的效果。詳情參考如何定位Obj-C野指針隨機(jī)Crash(一):先提高野指針Crash率、如何定位Obj-C野指針隨機(jī)Crash(二):讓非必現(xiàn)Crash變成必現(xiàn) 和如何定位Obj-C野指針隨機(jī)Crash(三):加點(diǎn)黑科技讓Crash自報家門

3、代碼中的注意事項

檢查使用assgin或 unsafe_unretained 修飾指向OC對象的指針 和 Core Foundation等底層操作。

三、內(nèi)存泄漏(Memory Leak)

1、概述

  • 內(nèi)存泄漏是指沒有釋放掉不再引用對象的內(nèi)存。即便ARC幫我們解決很多麻煩,但是內(nèi)存泄漏問題依然比較多;一般開發(fā)結(jié)束后,都要做一些基本的內(nèi)存泄漏排查工作。
  • 內(nèi)存泄漏排查,一般采用Analyzer(靜態(tài)分析) + Leaks + MLeaksFinder (第三方工具)

2-1、排查之靜態(tài)分析(Analyzer)

  • Xcode提供的 Analyzer可以在程序沒運(yùn)行的時候,通過分析代碼上下文的語法結(jié)構(gòu)和內(nèi)存情況,找出代碼中潛在錯誤,如內(nèi)存泄露、未使用函數(shù)和變量等。選中Product->Analyze(快捷鍵command+shift+B)可以使用了。
  • Analyzer主要分析四種問題:

1) 邏輯錯誤:訪問空指針或未初始化的變量等;

2) 內(nèi)存管理錯誤:如內(nèi)存泄漏等;Core Foundation不支持ARC

3) 聲明錯誤:從未使用過的變量;

4) API調(diào)用錯誤:未包含使用的庫和框架。

  • Analyzer執(zhí)行后,常見的警告類型有:

1)內(nèi)存警告(Memory)

eg:

  1. - (UIImage *)clipImageWithRect:(CGRect)rect{ 
  2.  
  3.   
  4.  
  5.   CGFloat scale = self.scale; 
  6.  
  7.   CGImageRef clipImageRef = CGImageCreateWithImageInRect(self.CGImage, 
  8.  
  9.                                                     CGRectMake(rect.origin.x * scale, 
  10.  
  11.                                                                rect.origin.y  * scale, 
  12.  
  13.                                                                rect.size.width * scale, 
  14.  
  15.                                                                rect.size.height * scale)); 
  16.  
  17.   
  18.  
  19.   CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(clipImageRef)/scale, CGImageGetHeight(clipImageRef)/scale); 
  20.  
  21.   UIGraphicsBeginImageContextWithOptions(smallBounds.size, YES, scale); 
  22.  
  23.   CGContextRef context = UIGraphicsGetCurrentContext(); 
  24.  
  25.   
  26.  
  27.   CGContextTranslateCTM(context, 0, smallBounds.size.height); 
  28.  
  29.   CGContextScaleCTM(context, 1.0, -1.0); 
  30.  
  31.   CGContextDrawImage(context, CGRectMake(0, 0, smallBounds.size.width, smallBounds.size.height), clipImageRef); 
  32.  
  33.   
  34.  
  35.   UIImage* clipImage = UIGraphicsGetImageFromCurrentImageContext(); 
  36.  
  37.   
  38.  
  39.   UIGraphicsEndImageContext(); 
  40.  
  41.   CGImageRelease(clipImageRef);  //不添加,內(nèi)存泄漏,會警告:Potential leak of an object stored into 'clipImageRef' 
  42.  
  43.   return clipImage; 
  44.  
  45.  

分析:Analyzer檢查出來內(nèi)存泄漏,比較常見的就是CG、CF開頭的內(nèi)存泄漏,內(nèi)存申請,忘記釋放了。還有一種是,C申請的內(nèi)存,沒有配對使用new delete, malloc free。

2)無效數(shù)據(jù)警告(Dead store)

eg:

  1. //錯誤做法,Analyzer分析后會告知:Value stored to ‘dataArray’ during its initialization is never read 
  2.  
  3. NSMutableArray *dataArray = [[NSMutableArray alloc] init]; 
  4.  
  5. dataArray = _otherDataArray; 
  6.  
  7.   
  8.  
  9. //正確做法 
  10.  
  11. NSMutableArray *dataArray = nil; 
  12.  
  13. dataArray = _otherDataArray;  

分析: dataArray已經(jīng)被初始化分配了內(nèi)存,然后被另一個可變數(shù)組賦值,導(dǎo)致一個數(shù)據(jù)源卻申請了兩塊內(nèi)存,造成了內(nèi)存泄露。

3)邏輯錯誤監(jiān)測(Logic error)

eg:

  1. //錯誤做法,Analyzer分析后會告知:Property of mutable type ’NSMutableArray’ has ‘copy’ attribute,an immutable object will be stored instead 
  2.  
  3. @property (nonatomic, copy) NSMutableArray *dataArr;   
  4.  
  5.   
  6.  
  7. //正確做法 
  8.  
  9. @property (nonatomic, strong) NSMutableArray *dataArr;  

分析: NSMutableArray是可變數(shù)據(jù)類型,應(yīng)該用strong來修飾其對象。

說明: Analyzer由于是編譯器根據(jù)代碼進(jìn)行的判斷, 做出的判斷不一定會準(zhǔn)確, 因此如果遇到提示, 應(yīng)該去結(jié)合代碼上文檢查一下;還有某些造成內(nèi)存泄漏的循環(huán)引用通過Analyzer分析不出來。

2-2、排查之內(nèi)存泄漏工具(Leaks)

  • Xcode提供的Leak可以幫助發(fā)現(xiàn)運(yùn)行著的程序內(nèi)存泄漏的地方。通過選中Product-> Profile(快捷鍵command+i,喚起Instrument工具界面) -> Leaks。切換到Call Tree模式,底部選中Separate by Thread(按線程分開做分析)、Invert Call Tree(反向輸出調(diào)用樹)、Hide System Libraries(隱藏系統(tǒng)庫文件)。最后點(diǎn)擊紅色按鈕開始“錄制”,效果如下圖:

 

Leaks調(diào)試界面.png

  • 在Leaks調(diào)試界面上,1是Allocations 模板,顯示內(nèi)存分配情況;2是 Leaks 模板,這里可以查看內(nèi)存泄露情況。如果紅X出現(xiàn), 表示有內(nèi)存泄露;主框體區(qū)域則會顯示泄露的對象。Call Tree選項介紹如下: 
CALL TREE 中選項 說明
Separate by Category 按類型分類,展開All Heap Allocations這一套顯示的就是不同方法里堆內(nèi)存的分配情況
Separate by Thread 按線程分開做分析,這樣更容易揪出那些吃資源的問題線程。特別是對于主線程,它要處理和渲染所有的接口數(shù)據(jù),一旦受到阻塞,程序必然卡頓或停止響應(yīng)。
Invert Call Tree 反向輸出調(diào)用樹。把調(diào)用層級最深的方法顯示在最上面,更容易找到最耗時的操作。
Hide System Libraries 隱藏系統(tǒng)庫文件。過濾掉各種系統(tǒng)調(diào)用,只顯示自己的代碼調(diào)用。
Flattern Recursion 拼合遞歸。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因為遞歸函數(shù)會調(diào)用自己)合并為一條

2-3、排查之MLeaksFinder(強(qiáng)烈推薦)

MLeaksFinder是微信閱讀團(tuán)隊為了簡化內(nèi)存泄漏排查工作,推出的第三方工具,也是我們當(dāng)前項目中內(nèi)存泄漏的工具之一。

  • 特點(diǎn):集成簡單,主要檢查UI方面(UIView 和 UIViewController)的泄漏。
  • 原理:不入侵開發(fā)代碼,通過hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,檢查ViewController對象被 pop 或 dismiss 一小段時間后,看看該ViewController對象的 view,view 的 subviews 等等是否還存在。
  • 實現(xiàn):為基類 NSObject 添加一個方法 -willDealloc 方法,利用weak指針指向自己,并在一小段時間(3秒)后,再次檢測該weak指針是否有效,有效則內(nèi)存泄漏。
  • 集成:通過Cocoapods引入或直接把代碼拖進(jìn)項目,很方便。發(fā)生內(nèi)存泄漏,會彈出警告框,提示發(fā)生內(nèi)存泄漏的位置。

說明:詳細(xì)內(nèi)容請參考:MLeaksFinder:精準(zhǔn) iOS 內(nèi)存泄露檢測工具和MLeaksFinder 新特性

3、代碼中的注意事項(ARC下的循環(huán)引用是內(nèi)存泄漏的主要原因)

  • 檢查代碼1 :Core Foundation、Core Graphics等操作

Core Foundation、CoreGraphics等操作不支持ARC,還需要手動內(nèi)存管理。

建議: 注意CF、CG對象的創(chuàng)建和釋放。

  • 檢查代碼2 :NSTimer/CADisplayLink的使用,因為NSTimer/CADisplayLink對象的target會強(qiáng)引用self,而self又強(qiáng)引用NSTimer/CADisplayLink對象。

建議:使用擴(kuò)展方法,使用block或 target弱引用目標(biāo)對象 打破保留環(huán),具體實現(xiàn)參考iOS實錄8:解決NSTimer/CADisplayLink的循環(huán)引用

  • 檢查代碼3 :block使用代碼。

建議:成對使用weakSelf和strongSelf來打破block循環(huán)引用(對于self沒有引用的block是不會造成循環(huán)引用,不需要使用weakSelf和strongSelf)

原理:在block外定義弱引用(weakSelf),指向的self對象;在block內(nèi)捕獲的是這個弱引用(weakSelf),保證了self不會被block所持有;在執(zhí)行block內(nèi)方法時,生成強(qiáng)引用(strongSelf),指向了弱引用(weakSelf)所指向的對象(self對象);在block內(nèi)部實際是持有了self對象,但是這個強(qiáng)引用(strongSelf) 的生命周期只在這個block執(zhí)行的過程中,block執(zhí)行執(zhí)行完立刻就被釋放了。

四、廢棄內(nèi)存(Abandoned Memory)

1、概述

  • 廢棄內(nèi)存(Abandoned Memory)指,依然被引用對象的內(nèi)存,但在程序邏輯中無法再被利用。
  • 排查該類問題建議使用Xcode提供的Allocation,Allocation可以跟蹤應(yīng)用的內(nèi)存分配情況。

2、使用Allocation

  • Xcode提供的Allocation由于可以跟蹤應(yīng)用的內(nèi)存分配情況。開發(fā)者反復(fù)操作App,查看內(nèi)存基線變化;甚至還可以設(shè)置Mark Generation來對比多次Generation之間的內(nèi)存增長,這部分的增長就是我們沒有及時釋放的內(nèi)存。通過Product-> Profile(快捷鍵command+i,喚起Instrument工具界面) -> Allocations。最后點(diǎn)擊紅色按鈕開始“錄制”,效果如下圖:

 

Allocation界面Statistics Detail 下顯示.png

  • 上圖是Statistics Detail Type下的界面展示,下面是一些名稱的說明:
DETAIL列名 說明
Graph 類型的選擇項
Category 類型,或CF對象,或OC對象,或原始塊的內(nèi)存
Persistent Bytes 未釋放的內(nèi)存和大小
Persistent 未釋放的對象個數(shù)
Transient 已經(jīng)釋放的對象個數(shù)
Total Bytes 總使用內(nèi)存大小
Total 總使用對象個數(shù)
Transient / Total Bytes 已釋放內(nèi)存大小/總使用內(nèi)存大小

 

ALLOCATION TYPE 說明
All Heap & Anonymous 所有堆內(nèi)存和其他內(nèi)存
All Heap Allocations 所有堆內(nèi)存
All Anonymous VM 所有其他內(nèi)存
  • 下圖是切換到Call Tree下的界面展示。 

 

Allocation界面Call Tree下顯示.png

CALL TREE列名 說明
Bytes Used 已經(jīng)使用的內(nèi)存大小
Count 符號使用的總個數(shù)
Symbol Name 符號名稱

說明:這些名詞的具體解釋見Instrument-Allocations

  • 間隔一段時間(如2分鐘)點(diǎn)擊“Mark Generation”,判斷幾次之間Generation之間的內(nèi)存增長,而這些增長可能就是未能及時釋放的內(nèi)存:根據(jù)內(nèi)存占用的比例,找到占用比例最高的那部分,然后找到我們自己的代碼,再來分析并解決問題。

 

Allocation界面Mark Generation下顯示.png

3、代碼中的注意事項

略,與內(nèi)存泄漏部分代碼中的注意事項相同。 

責(zé)任編輯:龐桂玉 來源: iOS大全
相關(guān)推薦

2017-07-21 14:00:00

iOSCrashMach異常

2019-07-11 06:22:51

Cgroups容器監(jiān)控

2010-05-26 14:42:54

桌面虛擬化

2011-07-07 11:03:07

iOS MVC Objective-

2011-07-28 10:01:19

IOS 內(nèi)存優(yōu)化

2018-10-16 15:08:20

屏幕圖像對象

2017-11-10 13:02:44

iOSUI代碼

2009-09-21 17:17:11

Hibernate二級

2011-08-02 10:50:56

iOS開發(fā) 內(nèi)存緩存

2018-04-09 13:47:39

Crash日志App

2011-07-28 17:20:55

2020-10-15 14:10:51

網(wǎng)絡(luò)攻擊溯源

2012-10-09 13:53:33

大型網(wǎng)站算法架構(gòu)

2011-07-29 09:45:11

iOS 圖形圖像 Core Anima

2014-07-29 15:44:33

Linux內(nèi)核Crash

2010-08-06 13:40:06

DB2建立nickna

2016-09-26 17:09:28

Java并發(fā)編程內(nèi)存模型

2012-10-29 11:25:05

IBMdw

2021-12-30 10:43:21

Android函數(shù)Crash

2017-01-19 18:58:11

iOS組件化方案
點(diǎn)贊
收藏

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