iOS 內(nèi)存泄漏分析


Leaks


蘋果官方有關(guān)于內(nèi)存分析說明纯衍,在文檔中把內(nèi)存泄漏情況分為4中:

Overall Memory Use. Monitor at a high level how your app uses memory and compare it to the memory usage of other active processes on the system. Look for areas of large or unexpected memory growth

Leaked Memory. This is memory that was allocated at some point, but was never released and is no longer referenced by your app. Since there are no references to it, there’s now no way to release it and the memory can’t be used again. For example, suppose you’ve written an app that creates rectangle objects in a drawing, but never releases the objects when the drawing is closed. In this case, your app would leak more and more memory whenever a drawing containing rectangles is closed. To fix the leak, you need to figure out which object isn’t being released, and then update your app to release it at the appropriate time.

Abandoned Memory. This is memory that your app has allocated for some reason, but it’s not needed and won’t be referenced. For example, suppose your app adds images to a cache after they’ve already been cached—using double the memory for the same images. Or, maybe your app maintains an array of objects in case you need to access them later, but you never actually do. Unlike leaked memory, abandoned memory like this is still referenced somewhere in your app. It just serves no purpose. Since it’s still technically valid, it’s more difficult for Instruments to identify and requires more detective work on your part to find

Zombies. This is memory that has been released and is no longer needed, but your code still references it somewhere. For example, suppose your app contains an image cache. Once the cache has been cleared, your app shouldn’t attempt to refer to the images that it previously contained. Calls to these nonexistent images are considered zombies—references to objects that are no longer living

Overall Memory Use: 內(nèi)存總體使用情況
leaked Memory: 在MRC中會經(jīng)常出現(xiàn)续扔,當(dāng)retain后忘記發(fā)送Release消息,導(dǎo)致一個對象沒有指針引用但retainCount != 0retainCount == 0才能讓對象銷毀)腋寨,ARC中很少出現(xiàn)紊扬。
Abandoned Memory: 在ARC中會經(jīng)常出現(xiàn)灾杰,當(dāng)retainrelease不在被用戶手動觸發(fā)匈棘,導(dǎo)致很多很多循環(huán)引用不為開發(fā)者所感知,而導(dǎo)致Abandoned memory耸采。
Zombies: 僵尸對象兴泥,當(dāng)對象已經(jīng)被釋放,但我們?nèi)匀挥弥羔樥{(diào)用時會出現(xiàn)這種情況虾宇。

內(nèi)存查看工具


Zombies

Xcode有自帶的Zombies exception提醒.
1搓彻、在 manager scheme -> run -> Diagnostics 中開啟即可。

Zombies

2嘱朽、在斷點欄點擊左下角的+號旭贬,添加一個 Add Exception Breakpoint

Add Exception Breakpoint

Leaked Memory

利用 xcode -> product ->profile -> leaks 查看在ARC中用的比較少搪泳,這里不做介紹

Abandoned Memory(重點介紹)

對于Abandoned memory稀轨,可以用instrument 的 Allocation方法檢測出來。主要原理:利用Mark Generation 對某一個操作進(jìn)行操作前后的快照對比岸军,通過堆中OC對象數(shù)量變化來判斷是否產(chǎn)生了內(nèi)存泄漏奋刽。

WWDC官方視頻:
Advanced Memory Analysis with Instruments

WWDC視頻中介紹了一個簡單的案例:在平時開發(fā)時,我們常常以一個UIViewController作為一個基礎(chǔ)元素凛膏,因此我們可以對一個UIViewController進(jìn)行觀察杨名。理論上脏榆,我們push這個VC猖毫,這個VC中所有的子View,ViewModel须喂,Model會分配一部分的內(nèi)存空間吁断,而在Pop之后應(yīng)該全部被dealloc掉趁蕊。因此我們可以在Push之前,Push之后仔役,Pop之后都設(shè)置一個快照掷伙,通過查看3個快照之間的差異判斷是否有內(nèi)存泄漏。

使用方法
1又兵、利用 xocode -> product -> profile 工具(command + i)開啟instrument任柜。

Profile

2、選擇Leaks查看工具

Leaks

3沛厨、打開后的界面選擇Allocations

Allocations

All Heap & Anonymous VM 里面能看到堆中的使用情況宙地。在右上角能進(jìn)行篩選,篩選后能看到具體細(xì)節(jié):

現(xiàn)在占有的個數(shù)(Persistent)逆皮,
占有內(nèi)存大小(Persistent Bytes),
總創(chuàng)建的個數(shù)(Total),
總創(chuàng)建所占內(nèi)存大小(Persistent)

這樣我們就可以開始通過操作頁面宅粥,動態(tài)監(jiān)測一個項目是否存在內(nèi)存泄漏。

常見內(nèi)存對象不銷毀情況

1电谣、UIViewController 和 子UIView中雙向持有對方
@interface MyViewController: UIViewController   
     //1.MyViewController 中強(qiáng)引用 childView 
    @property (nonatomic, strong) ChildView *childView;    
@end
----------------------------------------------------- 
@interface ChildView : UIView
    @property (nonatomic, strong) MyViewController *controller;
    -(instancetype)initWithController:(MyViewController *)controller;
@end
//implement
    -(instancetype)initWithController:(MyViewController *)controller{
        self = [super init];
        if(self){
            //2.這一步讓View秽梅,強(qiáng)引用 MyViewController
            self.controller = controller;
        }
        return self;
    }

如上代碼所示,1剿牺,2中讓兩個對象雙向強(qiáng)引用企垦,導(dǎo)致兩者都不會被釋放。
解決方案:
2這一步換成weak

@property (nonatomic, weak) MyViewController *controller;
2晒来、Delegate循環(huán)引用

還是引用上面的案例竹观,這次我們改成delegate版本

@interface MyViewController: UIViewController 
//1.MyViewController 中強(qiáng)引用 childView 
    @property (nonatomic, strong) ChildView *childView; 
@end 
//implement 
self.childView = [[ChildView alloc]init]; 
//2.delegate 設(shè)置為 MyViewController 
self.childView.delegate = self; 
----------------------------------------------------- 
@protocol ChildViewProtocol 
@end 
@interface ChildView : UIView 
    //strong delegate代理器 
    @property(nonatomic, strong) id<ChildViewProtocol> *delegate 
@end

1MyViewController強(qiáng)引用持有一個ChildView
2潜索、ChildView.delegate 設(shè)置為 MyViewController
3臭增、這里可以看出ChildView強(qiáng)引用id<ChildViewProtocol> == MyViewController
因此也造成了循環(huán)引用,導(dǎo)致不能被銷毀
解決方案
3中強(qiáng)引用改為弱引用

@property(nonatomic, weak) id<ChildViewProtocol> *delegate
3竹习、block使用雙向持有

這個比較復(fù)雜誊抛,首先我們了解下block原理:
A look inside blocks: Episode 1
A look inside blocks: Episode 2
這兒有兩篇文章對block具體實現(xiàn)講的很詳細(xì)≌埃總結(jié)一下文章的觀點拗窃,block的struct聲明如下:

struct Block_descriptor { 
    unsigned long int reserved; 
    unsigned long int size; 
    void (*copy)(void *dst, void *src); 
    void (*dispose)(void *); 
};  
struct Block_layout { 
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(void *, ...); 
    struct Block_descriptor *descriptor; 
    /* Imported variables. */ 
};

在這個struct,我們看到一個isa指針泌辫,這里和OC對象很類似随夸。在OC對象中isa指向的是其Class或者metaClass, 在這里isa指針震放,指向3種類型的Block

_NSConcreteGlobalBlock 全局Block宾毒,在編譯的時候則已經(jīng)存在
_NSConcreteStackBlock 棧上分配的Block,也就是在方法體內(nèi)部聲明的block
_NSConcreteMallocBlock 堆上分配的Block殿遂,也就是一個對象的成員變量或?qū)傩?/p>

而出現(xiàn)循環(huán)引用的情況诈铛,大多數(shù)都是_NSConcreteMallocBlock使用不恰到導(dǎo)致乙各。下面看一個具體案例:

//1.定義一個TestBlock 
typedef void (^TestBlock)();  
//2.TestBlock的初始化 
TestBlock getBlock() { 
    char e = 'E'; 
    void (^returnedBlock)() = ^{ 
        printf("%c\n", e); 
    }; 
    return returnedBlock; 
}

首先我們先看下2,在棿敝瘢空間創(chuàng)建一個returnedBlock耳峦,這個block在方法體執(zhí)行完后會自動銷毀。在return returnedBlock焕毫,在ARC中其實系統(tǒng)會自動幫你做一次copy操作蹲坷,而這次copy操作則讓block從_NSConcreteStackBlock變?yōu)榱?code>_NSConcreteMallocBlock。
如果還不清楚邑飒,可以看下MRC情況下冠句,block一般使用方案:

TestBlock getBlock() { 
    char e = 'E'; 
    void (^returnedBlock)() = ^{ 
        printf("%c\n", e); 
    }; 
        //3.手動copy,然后autoRelease
   return [[returnedBlock copy] autorelease]; 
}

3中幸乒,很容易看出需要手動進(jìn)行一次copy操作懦底,而這次copy操作讓這個block 的retainCount屬性 +1.

block 循環(huán)引用 案例1
所以block本質(zhì)上類似上面第1,2案例的ChildView

//1罕扎、self有個指針強(qiáng)引用 completionBlock 
@property(nonatomic, readwrite, copy) completionBlock completionBlock; 
@property(nonatomic, strong) UIView *successView;      
self.completionBlock = ^ { 
    //2聚唐、在這里使用self,則堆中block空間會生成一個指針指向self腔召,形成了一個雙向強(qiáng)引用 
    self.successView.hidden = YES;
};

如注釋1,2所示杆查,隱形中形成雙向引用,解決方案:

//生成一個對 self 的弱引用 
__weak typeof(self) weakSelf = self; 
self.completionBlock = ^ {  
    weakSelf.successView.hidden = YES;
};

block 循環(huán)引用 案例2
最近在研究 ReactiveCocoa臀蛛,這個框架對 self 弱引用亲桦,強(qiáng)引用進(jìn)行封裝,如@weakify(self) @strongify(self)浊仆,這有篇文章對著兩個宏定義進(jìn)行剖析 剖析@weakify 和 @strongify客峭,文章分析最終結(jié)果為:

"@weakify(self)" == "__weak __typeof__ (self) self_weak_ = self;"
"@strongify(self)" == "__strong __typeof__ (self) self = self_weak_;"

所以案例1方案另一種寫法為:

//利用 ReactiveCocoa 方案 
@weakify(self) 
self.completionBlock = ^ {  
    @strongify(self)  
    self.successView.hidden = YES; 
};

block 循環(huán)引用 案例3
下面繼續(xù)介紹@weakify(self) @strongify(self)在使用中遇到的坑。如果block里面嵌套block抡柿,那該如何解決舔琅,先看下面案例:

@weakify(self) 
//1、給selectedButton綁定一個點擊事件
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside] 
subscribeNext:^(id x) { 
//2洲劣、點擊以后做數(shù)據(jù)操作 
    self.do(^(BOOL result){ 
        //3备蚓、操作完成后,回調(diào)處理 
        @strongify(self) 
         self.success = result; 
    }) 
}];

1囱稽、創(chuàng)建一個selectedButton的點擊事件
2郊尝、點擊事件觸發(fā)后,進(jìn)行do操作
3战惊、最后對操作后的事件進(jìn)行處理 self.success = result

當(dāng)利用instrument測試代碼的時流昏,我們會發(fā)現(xiàn)這個block會造成循環(huán)引用。如果對@weakify(self) @strongify(self)不理解,很難發(fā)現(xiàn)其中問題横缔。原因在哪里?我們按照上面宏替換標(biāo)準(zhǔn)對這個進(jìn)行替換衫哥。

__weak __typeof__ (self) self_weak_ = self;  
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]         //1 
subscribeNext:^(id x) { 
    self.do(^(BOOL result){         //2 
        __strong __typeof__ (self) self = self_weak_;  
        self.success = result;         //3 
    }) 
}];

我們對上面代碼中self進(jìn)行分析

  1. 這里是強(qiáng)引用self持有一個block
  2. block 持有一個strongself 因此會導(dǎo)致循環(huán)引用
  3. 這里的self實際上是self_weak_ 沒有問題

所以問題出現(xiàn)在2處茎刚!那下面我進(jìn)行多次嘗試修復(fù)這個問題。

嘗試修改1

@weakify(self) 
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]       //1 
subscribeNext:^(id x) { 
    @strongify(self) 
    @weakify(self) 
    self.do(^(BOOL result){     //2  
        @strongify(self)  
        self.success = result;      //3 
    }) 
}];

這樣修改最能適應(yīng)@weakify(self) @strongify(self)ReactiveCocoa成對出現(xiàn)的理念撤逢,且在2處使用的肯定是 weak 的self膛锭,確實沒有問題。

但總感覺有點奇怪蚊荣,利用上面的宏替換很容易看出初狰,在第一次@strongify(self)時,self == self_weak_ self已經(jīng)是weak互例,所以我們沒必要再在后面進(jìn)行新的 weakifystrongify奢入,對上面的代碼進(jìn)行改進(jìn),如下

嘗試修改2

@weakify(self) 
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]  
subscribeNext:^(id x) { 
    @strongify(self) 
    self.do(^(BOOL result){  
        self.success = result;  
    }) 
}];

總結(jié):多層嵌套的block媳叨,只需要對最外層block傳入的self進(jìn)行weak化即可腥光。

block 循環(huán)引用 案例4

再列舉一個容易犯的錯誤,代碼如下:

@property (nonatomic, assign) BOOL success; 
@weakify(self)     
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]  
subscribeNext:^(id x) { 
    @strongify(self) 
    _success = false;       //1 
}];

問題就出現(xiàn)在1處糊秆。我們知道對屬性的訪問武福,可以使用 self.success_success兩種方式,當(dāng)然兩者有一些區(qū)別

self.success 能支持懶加載方式 調(diào)用 successget 方法痘番。
_success是對實例變量的訪問捉片。
iOS 5.0 之后已經(jīng)支持屬性到實例的映射,也就是省略 @sychronise _success = self.success;

但在block中使用汞舱,得特別注意伍纫,self.success 會使用 @strongify(self) 所生成的self_weak_ ,而_success 不會昂芜!不會翻斟!不會!
所以block 強(qiáng)引用指向 strong 的 self说铃,調(diào)用其實例變量访惜。所以上訴代碼 _success會造成循環(huán)引用。

4腻扇、NSNotificationCenter债热,KVO 問題

關(guān)于事件監(jiān)聽,屬性監(jiān)聽幼苛,會自動retain self窒篱,所以在 dealloc 時需要對監(jiān)聽進(jìn)行釋放。

[[NSNotificationCenter defaultCenter] removeObserver:self]; 
[self removeObserver:self forKeyPath:@"" context:nil];
5、NSTimer Animator 停止

對于NSTimer持續(xù)的Animator動畫墙杯,需要在 dealloc時停止配并。

[NSTimer invalidate];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市高镐,隨后出現(xiàn)的幾起案子溉旋,更是在濱河造成了極大的恐慌,老刑警劉巖嫉髓,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件观腊,死亡現(xiàn)場離奇詭異,居然都是意外死亡算行,警方通過查閱死者的電腦和手機(jī)梧油,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來州邢,“玉大人儡陨,你說我怎么就攤上這事×刻剩” “怎么了迄委?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長类少。 經(jīng)常有香客問我叙身,道長,這世上最難降的妖魔是什么硫狞? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任信轿,我火速辦了婚禮,結(jié)果婚禮上残吩,老公的妹妹穿的比我還像新娘财忽。我一直安慰自己,他們只是感情好泣侮,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布即彪。 她就那樣靜靜地躺著,像睡著了一般活尊。 火紅的嫁衣襯著肌膚如雪隶校。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天蛹锰,我揣著相機(jī)與錄音深胳,去河邊找鬼。 笑死铜犬,一個胖子當(dāng)著我的面吹牛舞终,可吹牛的內(nèi)容都是我干的轻庆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼敛劝,長吁一口氣:“原來是場噩夢啊……” “哼余爆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夸盟,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蛾方,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后满俗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體转捕,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡作岖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年唆垃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痘儡。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡辕万,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沉删,到底是詐尸還是另有隱情渐尿,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布矾瑰,位于F島的核電站砖茸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏殴穴。R本人自食惡果不足惜凉夯,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望采幌。 院中可真熱鬧劲够,春花似錦、人聲如沸休傍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磨取。三九已至人柿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忙厌,已是汗流浹背顷扩。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留慰毅,地道東北人隘截。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婶芭。 傳聞我的和親對象是個殘疾皇子东臀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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