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中會(huì)經(jīng)常出現(xiàn),當(dāng)retain
后忘記發(fā)送Release
消息,導(dǎo)致一個(gè)對象沒有指針引用但retainCount != 0
(retainCount == 0
才能讓對象銷毀),ARC中很少出現(xiàn)。
Abandoned Memory:
在ARC中會(huì)經(jīng)常出現(xiàn),當(dāng)retain
和release
不在被用戶手動(dòng)觸發(fā),導(dǎo)致很多很多循環(huán)引用不為開發(fā)者所感知,而導(dǎo)致Abandoned memory。
Zombies:
僵尸對象,當(dāng)對象已經(jīng)被釋放,但我們?nèi)匀挥弥羔樥{(diào)用時(shí)會(huì)出現(xiàn)這種情況。
內(nèi)存查看工具
Zombies
Xcode有自帶的Zombies exception提醒.
1、在 manager scheme -> run -> Diagnostics 中開啟即可。
2、在斷點(diǎn)欄點(diǎn)擊左下角的+號,添加一個(gè) Add Exception Breakpoint 。
Leaked Memory
利用 xcode -> product ->profile -> leaks 查看在ARC中用的比較少,這里不做介紹
Abandoned Memory(重點(diǎn)介紹)
對于Abandoned memory,可以用instrument 的 Allocation方法檢測出來。主要原理:利用Mark Generation 對某一個(gè)操作進(jìn)行操作前后的快照對比,通過堆中OC對象數(shù)量變化來判斷是否產(chǎn)生了內(nèi)存泄漏。
WWDC官方視頻:
Advanced Memory Analysis with Instruments
WWDC視頻中介紹了一個(gè)簡單的案例:在平時(shí)開發(fā)時(shí),我們常常以一個(gè)UIViewController
作為一個(gè)基礎(chǔ)元素,因此我們可以對一個(gè)UIViewController
進(jìn)行觀察。理論上,我們push這個(gè)VC,這個(gè)VC中所有的子View,ViewModel,Model會(huì)分配一部分的內(nèi)存空間,而在Pop之后應(yīng)該全部被dealloc掉。因此我們可以在Push之前,Push之后,Pop之后都設(shè)置一個(gè)快照,通過查看3個(gè)快照之間的差異判斷是否有內(nèi)存泄漏。
使用方法
1、利用 xocode -> product -> profile 工具(command + i
)開啟instrument。
2、選擇Leaks查看工具
3、打開后的界面選擇Allocations
All Heap & Anonymous VM
里面能看到堆中的使用情況。在右上角能進(jìn)行篩選,篩選后能看到具體細(xì)節(jié):
現(xiàn)在占有的個(gè)數(shù)
(Persistent)
,
占有內(nèi)存大小(Persistent Bytes)
,
總創(chuàng)建的個(gè)數(shù)(Total)
,
總創(chuàng)建所占內(nèi)存大小(Persistent)
這樣我們就可以開始通過操作頁面,動(dòng)態(tài)監(jiān)測一個(gè)項(xiàng)目是否存在內(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
中讓兩個(gè)對象雙向強(qiáng)引用,導(dǎo)致兩者都不會(huì)被釋放。
解決方案:
把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
1
、MyViewController
強(qiáng)引用持有一個(gè)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使用雙向持有
這個(gè)比較復(fù)雜,首先我們了解下block
原理:
A look inside blocks: Episode 1
A look inside blocks: Episode 2
這兒有兩篇文章對block
具體實(shí)現(xiàn)講的很詳細(xì)。總結(jié)一下文章的觀點(diǎn),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. */
};
在這個(gè)struct,我們看到一個(gè)isa
指針,這里和OC對象很類似。在OC對象中isa
指向的是其Class
或者metaClass
, 在這里isa
指針,指向3種類型的Block
_NSConcreteGlobalBlock
全局Block,在編譯的時(shí)候則已經(jīng)存在
_NSConcreteStackBlock
棧上分配的Block,也就是在方法體內(nèi)部聲明的block
_NSConcreteMallocBlock
堆上分配的Block,也就是一個(gè)對象的成員變量或?qū)傩?/p>
而出現(xiàn)循環(huán)引用的情況,大多數(shù)都是_NSConcreteMallocBlock
使用不恰到導(dǎo)致。下面看一個(gè)具體案例:
//1.定義一個(gè)TestBlock
typedef void (^TestBlock)();
//2.TestBlock的初始化
TestBlock getBlock() {
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
return returnedBlock;
}
首先我們先看下2
,在棧空間創(chuàng)建一個(gè)returnedBlock
,這個(gè)block在方法體執(zhí)行完后會(huì)自動(dòng)銷毀。在return returnedBlock
,在ARC中其實(shí)系統(tǒng)會(huì)自動(dòng)幫你做一次copy
操作,而這次copy
操作則讓block從_NSConcreteStackBlock
變?yōu)榱?code>_NSConcreteMallocBlock。
如果還不清楚,可以看下MRC情況下,block一般使用方案:
TestBlock getBlock() {
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
//3.手動(dòng)copy,然后autoRelease
return [[returnedBlock copy] autorelease];
}
在3
中,很容易看出需要手動(dòng)進(jìn)行一次copy
操作,而這次copy
操作讓這個(gè)block 的retainCount
屬性 +1.
block 循環(huán)引用 案例1
所以block
本質(zhì)上類似上面第1,2案例的ChildView
//1、self有個(gè)指針強(qiáng)引用 completionBlock
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
@property(nonatomic, strong) UIView *successView;
self.completionBlock = ^ {
//2、在這里使用self,則堆中block空間會(huì)生成一個(gè)指針指向self,形成了一個(gè)雙向強(qiáng)引用
self.successView.hidden = YES;
};
如注釋1,2
所示,隱形中形成雙向引用,解決方案:
//生成一個(gè)對 self 的弱引用
__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
weakSelf.successView.hidden = YES;
};
block 循環(huán)引用 案例2
最近在研究 ReactiveCocoa,這個(gè)框架對 self 弱引用,強(qiáng)引用進(jìn)行封裝,如@weakify(self)
@strongify(self)
,這有篇文章對著兩個(gè)宏定義進(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綁定一個(gè)點(diǎn)擊事件
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
//2、點(diǎn)擊以后做數(shù)據(jù)操作
self.do(^(BOOL result){
//3、操作完成后,回調(diào)處理
@strongify(self)
self.success = result;
})
}];
1、創(chuàng)建一個(gè)
selectedButton
的點(diǎn)擊事件
2、點(diǎn)擊事件觸發(fā)后,進(jìn)行do
操作
3、最后對操作后的事件進(jìn)行處理self.success = result
當(dāng)利用instrument
測試代碼的時(shí),我們會(huì)發(fā)現(xiàn)這個(gè)block會(huì)造成循環(huán)引用。如果對@weakify(self)
@strongify(self)
不理解,很難發(fā)現(xiàn)其中問題。原因在哪里?我們按照上面宏替換標(biāo)準(zhǔn)對這個(gè)進(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)行分析
- 這里是強(qiáng)引用
self
持有一個(gè)block
block
持有一個(gè)strong
的self
因此會(huì)導(dǎo)致循環(huán)引用- 這里的self實(shí)際上是
self_weak_
沒有問題
所以問題出現(xiàn)在2
處!那下面我進(jìn)行多次嘗試修復(fù)這個(gè)問題。
嘗試修改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,確實(shí)沒有問題。
但總感覺有點(diǎn)奇怪,利用上面的宏替換很容易看出,在第一次@strongify(self)
時(shí),self == self_weak_
self
已經(jīng)是weak
,所以我們沒必要再在后面進(jìn)行新的 weakify
和 strongify
,對上面的代碼進(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
再列舉一個(gè)容易犯的錯(cuò)誤,代碼如下:
@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)用success
的get
方法。
_success
是對實(shí)例變量的訪問。
在iOS 5.0
之后已經(jīng)支持屬性到實(shí)例的映射,也就是省略@sychronise _success = self.success
;
但在block中使用,得特別注意,self.success
會(huì)使用 @strongify(self)
所生成的self_weak_
,而_success
不會(huì)!不會(huì)!不會(huì)!
所以block 強(qiáng)引用指向 strong 的 self
,調(diào)用其實(shí)例變量。所以上訴代碼 _success
會(huì)造成循環(huán)引用。
4、NSNotificationCenter,KVO 問題
關(guān)于事件監(jiān)聽,屬性監(jiān)聽,會(huì)自動(dòng)retain self
,所以在 dealloc
時(shí)需要對監(jiān)聽進(jìn)行釋放。
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self removeObserver:self forKeyPath:@"" context:nil];
5、NSTimer Animator 停止
對于NSTimer
和 持續(xù)的Animator動(dòng)畫
,需要在 dealloc
時(shí)停止。
[NSTimer invalidate];