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 != 0
(retainCount == 0
才能讓對象銷毀)腋寨,ARC中很少出現(xiàn)紊扬。
Abandoned Memory:
在ARC中會經(jīng)常出現(xiàn)灾杰,當(dāng)retain
和release
不在被用戶手動觸發(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 中開啟即可。
2嘱朽、在斷點欄點擊左下角的+號旭贬,添加一個 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任柜。
2、選擇Leaks查看工具
3沛厨、打開后的界面選擇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
1
、MyViewController
強(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)行分析
- 這里是強(qiáng)引用
self
持有一個block
block
持有一個strong
的self
因此會導(dǎo)致循環(huán)引用- 這里的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)行新的 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
再列舉一個容易犯的錯誤,代碼如下:
@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
是對實例變量的訪問捉片。
在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];