# 前言
反復(fù)地復(fù)習(xí)iOS基礎(chǔ)知識(shí)和原理棕硫,打磨知識(shí)體系是非常重要的,本篇就是重新溫習(xí)iOS的內(nèi)存管理徘溢。
內(nèi)存管理是管理對(duì)象生命周期景东,在對(duì)象不需要時(shí)進(jìn)行內(nèi)存釋放的編程規(guī)范砂轻。
# 目錄
前言
目錄
-
MRC時(shí)代
- 概要
- Memory Management Policy 內(nèi)存管理策略
- Practical Memory Management 實(shí)際內(nèi)存管理
- 內(nèi)存管理實(shí)踐
- 使用訪問(wèn)器方法使內(nèi)存管理更輕松
- 使用訪問(wèn)器方法設(shè)置屬性
- 不要在初始化和dealloc中使用訪問(wèn)器方法
- 使用弱引用來(lái)避免循環(huán)引用
- 避免正在使用的對(duì)象被釋放
- Collections類(lèi)擁有它們所包含的對(duì)象所有權(quán)
- 通過(guò)引用計(jì)數(shù)實(shí)現(xiàn)所有所有權(quán)策略
-
ARC時(shí)代
- 概要
- ARC 強(qiáng)制新規(guī)則
- 內(nèi)存泄漏
- block使用中出現(xiàn)循環(huán)引用
- NSTimer循環(huán)引用
參考資料
# MRC時(shí)代
概要
Objective-C內(nèi)存管理使用使用引用計(jì)數(shù)(Reference Counting)來(lái)管理內(nèi)存。
在OS X 10.8以后也不再使用垃圾回收機(jī)制斤吐,iOS則從來(lái)都沒(méi)有支持垃圾回收機(jī)制搔涝。
當(dāng)create
或者copy
對(duì)象時(shí)厨喂,會(huì)計(jì)數(shù)為1,其他對(duì)象需要retain
時(shí)庄呈,會(huì)增加引用計(jì)數(shù)蜕煌。持有對(duì)象的所有者也可以放棄所有權(quán),放棄所有權(quán)時(shí)減少計(jì)數(shù)诬留,當(dāng)計(jì)數(shù)為0時(shí)就會(huì)釋放對(duì)象斜纪。
如圖:
Memory Management Policy 內(nèi)存管理策略
- 通過(guò)分配內(nèi)存或
copy
來(lái)創(chuàng)建任何對(duì)象 - 使用方法
alloc
,allocWithZone:
,copy
,copyWithZone:
,mutableCopy
,mutableCopyWithZone:
創(chuàng)建對(duì)象 - 通過(guò)
retain
來(lái)獲取不是自己創(chuàng)建對(duì)象的所有權(quán)。以下兩種情況使用retain
:
- 在
accessor method
或者init method
方法獲取所需要的對(duì)象所有權(quán)為屬性property
文兑。 - 需要操作對(duì)象時(shí)盒刚,避免對(duì)象被釋放而導(dǎo)致錯(cuò)誤,需要
retain
持有對(duì)象绿贞。
- 發(fā)送
release
,autorelease
消息來(lái)釋放不需要的對(duì)象因块。 - 不要不是你創(chuàng)建的對(duì)象和沒(méi)有所有權(quán)的對(duì)象發(fā)送
release
消息。
Practical Memory Management 實(shí)際內(nèi)存管理
- Autorelease pools
- 向?qū)ο蟀l(fā)送
autorelease
消息籍铁,會(huì)將對(duì)象標(biāo)記為延遲釋放涡上,當(dāng)對(duì)象超出當(dāng)前作用域時(shí),釋放對(duì)象拒名。 -
AppKit frameworks
和UIKit frameworks
在事件循環(huán)的每個(gè)周期開(kāi)始時(shí)吩愧,在主線程上創(chuàng)建一個(gè)自動(dòng)釋放池,并在此次時(shí)間循環(huán)結(jié)束時(shí)靡狞,釋放它耻警,從而釋放在處理時(shí)生成的所有自動(dòng)釋放的對(duì)象。因此甸怕,通常不需要自己創(chuàng)建autoreleasePool
,當(dāng)然腮恩,以下情況你需要自己創(chuàng)建和銷(xiāo)毀autoreleasePool
:
- 如果你編寫(xiě)的代碼不是基于
UI framework
的程序梢杭,如command-line tool
命令行工具。 - 如果你需要寫(xiě)一個(gè)循環(huán)秸滴,創(chuàng)建許多臨時(shí)對(duì)象武契,如讀入大量的銅像同時(shí)改變圖片尺寸,圖像讀入到
NSData
對(duì)象荡含,并從中生成UIImage
對(duì)象咒唆,改變?cè)搶?duì)象尺寸生成新的UIImage
對(duì)象。 - 如果你創(chuàng)建一個(gè)長(zhǎng)期存在線程并且可能產(chǎn)生大量的
autorelease
對(duì)象释液。
autoreleasePool
推薦使用以下方法:
@autoreleasepool {
//do something
}
- dealloc
當(dāng)NSObject
對(duì)象的引用計(jì)數(shù)為0時(shí)全释,銷(xiāo)毀該對(duì)象前會(huì)調(diào)用dealloc
方法,用來(lái)釋放該對(duì)象擁有的所有資源误债,包裹實(shí)例變量指向的對(duì)象浸船。
例子:
// MRC
- (void)dealloc{
[_firstName release];
[_lastName release];
[super dealloc];
}
Important: Never invoke another object’s dealloc method directly.You must invoke the superclass’s implementation at the end of your implementation.You should not tie management of system resources to object lifetimes; see Don’t Use dealloc to Manage Scarce Resources.When an application terminates, objects may not be sent a dealloc message. Because the process’s memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods.
不要直接調(diào)用另一個(gè)對(duì)象的dealloc方法妄迁。你必須在類(lèi)使用結(jié)束時(shí)調(diào)用父類(lèi)的實(shí)現(xiàn)。你不應(yīng)該把系統(tǒng)資源與對(duì)象的生命周期綁定李命。
因?yàn)檫M(jìn)程的內(nèi)存退出時(shí)登淘,對(duì)象可能無(wú)法發(fā)送dealloc消息,該方法的內(nèi)存被自動(dòng)退出清零,所以讓操作系統(tǒng)清理資源比調(diào)用所有的內(nèi)存管理方法更有效。
內(nèi)存管理實(shí)踐
使用訪問(wèn)器方法使內(nèi)存管理更輕松
如果類(lèi)有一個(gè)屬性是一個(gè)對(duì)象封字,你必須確保使用該對(duì)象時(shí)黔州,它不會(huì)被釋放。因此在設(shè)置時(shí)阔籽,必須聲明對(duì)象的所有權(quán)流妻。還必須保證持有這些對(duì)象所有權(quán)的放棄。
- 使用
set
和get
方法來(lái)實(shí)現(xiàn)仿耽,更方便管理內(nèi)存(主要是省寫(xiě)很多retain
和release
)合冀。
例子如下:
@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;
Counter
類(lèi)有一個(gè)屬性是NSNumber
對(duì)象,屬性聲明了set
和get
兩個(gè)訪問(wèn)器方法项贺,在get
中就是返回synthesized
實(shí)例變量君躺,所以沒(méi)必要retain
或者release
:
- (NSNumber *)count {
return _count;
}
set
方法:
- (void)setCount:(NSNumber *)newCount {
[newCount retain]; // 先`retain`確保新數(shù)據(jù)不被釋放
[_count release]; // 釋放舊對(duì)象所有權(quán)
// Make the new assignment.
_count = newCount; // 將新值賦給_count
}
先retain
確保新數(shù)據(jù)不被釋放,釋放舊的對(duì)象所有權(quán)(Objective-C允許向nil
發(fā)送消息)开缎。你必須在[newCount retain]
之后再[_count release]
確保外部不會(huì)被dealloc
棕叫。
使用訪問(wèn)器方法設(shè)置屬性
// 方法一
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
// 方法二
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
方法二沒(méi)有對(duì)count
屬性賦新值時(shí)沒(méi)有使用set
訪問(wèn)方法,也不會(huì)觸發(fā)KVO
奕删,可能在特殊情況導(dǎo)致錯(cuò)誤(比如忘記了 retain
或者release
俺泣,或者如果實(shí)例變量的內(nèi)存管理發(fā)生了變化)。除了第一種方法完残,或者直接使用self.count = zero;
伏钠。
不要在初始化和dealloc中使用訪問(wèn)器方法
不應(yīng)該使用set
和get
方法在init
和dealloc
。應(yīng)該使用_
直接訪問(wèn)成員變量進(jìn)行初始化和dealloc
谨设。如下:
- init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
// 由于Counter類(lèi)具有對(duì)象實(shí)例變量熟掂,因此還必須實(shí)現(xiàn)dealloc方法。
// 它應(yīng)該通過(guò)向任何實(shí)例變量發(fā)送一個(gè)釋放消息來(lái)放棄它的所有權(quán)扎拣,最終它應(yīng)該調(diào)用super的實(shí)現(xiàn)
- (void)dealloc {
[_count release];
[super dealloc];
}
使用弱引用來(lái)避免循環(huán)引用
-
retain
對(duì)象赴肚,實(shí)際是對(duì)對(duì)象的強(qiáng)引用(strong reference),一個(gè)對(duì)象在所有強(qiáng)引用都沒(méi)有被釋放之前,不能釋放對(duì)象二蓝。因此誉券,如果有兩個(gè)對(duì)象互相持有對(duì)方或者間接互相引用,會(huì)導(dǎo)致循環(huán)引用刊愚。這時(shí)候就需要弱引用對(duì)方來(lái)打破這個(gè)循環(huán)踊跟。
如父親強(qiáng)引用兒子,兒子強(qiáng)引用孫子百拓,那么倒過(guò)來(lái)孫子只能弱引用兒子琴锭,兒子也只能弱引用父親晰甚。Cocoa
建立了一個(gè)約定,副對(duì)象應(yīng)該強(qiáng)引用子對(duì)象决帖,并且子對(duì)象應(yīng)該只對(duì)父對(duì)象弱引用厕九。
Cocoa
中常見(jiàn)的例子包括代理方法delegate
,data source
,observer
,target
等等
必須小心將消息發(fā)送到持有只是一個(gè)弱引用的對(duì)象地回。當(dāng)發(fā)送消息給一個(gè)被dealloc
的弱引用對(duì)象時(shí)扁远,你的應(yīng)用程序會(huì)崩潰(這是在MRC
時(shí)期的代理delegate
會(huì)出現(xiàn),因?yàn)楫?dāng)時(shí)對(duì)代理弱引用的修飾符是assign
,assign
弱引用并不會(huì)在對(duì)象dealloc
時(shí)刻像,把對(duì)象置為nil
畅买。而ARC
時(shí)代使用weak
則會(huì)在對(duì)象dealloc
時(shí)置為nil
)。
避免正在使用的對(duì)象被釋放
-
Cocoa
的所有權(quán)策略規(guī)定接收的對(duì)象通常在整個(gè)調(diào)用方法的范圍內(nèi)保證有效细睡。還應(yīng)該是在當(dāng)前方法范圍內(nèi)谷羞,而不必?fù)?dān)心它被釋放。對(duì)象的getter
方法返回一個(gè)緩存的實(shí)例變量或者一個(gè)計(jì)算的值溜徙,這不重要湃缎,重要的是,對(duì)象在需要的使用時(shí)還是有效的蠢壹。 - 有兩類(lèi)例外情況:
- 當(dāng)一個(gè)對(duì)象從基本的集合類(lèi)刪除時(shí)
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject 現(xiàn)在可能無(wú)效
-
n
從集合array
刪除時(shí)也會(huì)向n
發(fā)送release
(而不是autorelease
)消息嗓违。如果array
集合時(shí)被刪除n
對(duì)象的唯一擁有者,被移除的對(duì)象n
是立即被釋放的图贸。heisenObject
并沒(méi)有對(duì)n
進(jìn)行retain
蹂季,所以當(dāng)n
從array
刪除時(shí)同時(shí)被釋放。
正確的做法
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];
- 當(dāng)一個(gè)父對(duì)象被釋放時(shí)
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject 現(xiàn)在可能無(wú)效
- 在某些情況下疏日,從另一個(gè)對(duì)象獲取的對(duì)象偿洁,然后直接或者間接的釋放負(fù)對(duì)象。如果釋放父對(duì)象導(dǎo)致它被釋放沟优,并且父對(duì)象是子對(duì)象唯一所有者父能,那么子對(duì)象
heisenObject
將被同一時(shí)間釋放。所以正確的做法還是子對(duì)象heisenObject
獲取的時(shí)候先retain
一次净神。
Collections類(lèi)擁有它們所包含的對(duì)象所有權(quán)
- 添加一個(gè)對(duì)象到一個(gè)
collection
中,如(數(shù)組溉委、字典鹃唯、集合)時(shí),collection
會(huì)得到該對(duì)象所有權(quán)瓣喊。當(dāng)對(duì)象從collection
刪除或者collection
自己被釋放時(shí)坡慌,collection
將釋放它擁有的所有權(quán)。
NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
[array addObject:allocedNumber];
[allocedNumber release];
}
通過(guò)引用計(jì)數(shù)實(shí)現(xiàn)所有所有權(quán)策略
所有圈策略是通過(guò)引用計(jì)數(shù)實(shí)現(xiàn)的藻三,通常
retain
方法后被稱(chēng)為retain count
洪橘。每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)跪者。當(dāng)你創(chuàng)建一個(gè)對(duì)象,它的引用計(jì)數(shù)為
1
當(dāng)你給對(duì)象發(fā)送
retain
消息熄求,引用計(jì)數(shù)+1
當(dāng)你給對(duì)象發(fā)送
release
消息渣玲,引用計(jì)數(shù)-1
當(dāng)你給對(duì)象發(fā)送一個(gè)
autorelease
消息,它的引用計(jì)數(shù)器將在當(dāng)前的自動(dòng)釋放池結(jié)束后-1
當(dāng)對(duì)象的引用計(jì)數(shù)為
0
時(shí)將被釋放
# ARC時(shí)代
概要
iOS5后出現(xiàn)了ARC
弟晚。那么ARC
是什么呢忘衍?
自動(dòng)引用計(jì)數(shù)ARC
是一種編譯器的功能,為Objective-C
對(duì)象提供了自動(dòng)化的內(nèi)存管理卿城。
在ARC
不需要開(kāi)發(fā)者考慮保留或者釋放的操作枚钓,就是不用自己手動(dòng)retain
、release
和autorelease
(??開(kāi)心)瑟押,讓開(kāi)發(fā)者可以專(zhuān)注寫(xiě)有趣的代碼搀捷。
當(dāng)然ARC
依然是基于引用計(jì)數(shù)管理內(nèi)存。
ARC 強(qiáng)制新規(guī)則
ARC
相對(duì)于MRC
強(qiáng)制加了一些新的規(guī)則多望。
- 你不能主動(dòng)調(diào)用
dealloc
嫩舟、或者調(diào)用retain
,release
,retainCount
,autorelease
就是這些都不用你寫(xiě)了。也不能@selector(retain)
,@selector(release)
這樣子調(diào)用便斥。 - 你可以實(shí)現(xiàn)一個(gè)
dealloc
方法至壤,如果你需要管理資源而不是釋放實(shí)例變量(比如解除監(jiān)聽(tīng)、釋放引用枢纠、socket close等等)像街。在重寫(xiě)dealloc
后需要[super dealloc]
(在手動(dòng)管理引用計(jì)數(shù)時(shí)才需要)。 - 仍然可以使用
CFRetain
晋渺,CFRelease
等其它對(duì)象镰绎。 - 你不能使用
NSAllocateObject
或者NSDeallocateObject
。 - 你不能使用
C
結(jié)構(gòu)體木西,可以創(chuàng)建一個(gè)Objective-C
類(lèi)去管理數(shù)據(jù)而不是一個(gè)結(jié)構(gòu)體畴栖。 -
id
和void
沒(méi)有轉(zhuǎn)換關(guān)系,你必須使用cast
特殊方式,以便在作為函數(shù)參數(shù)傳遞的Objective-C
對(duì)象和Core Foundation
類(lèi)型之間進(jìn)行轉(zhuǎn)換八千。 - 你不能使用
NSAutoreleasePool
吗讶,使用@autoreleasepool
。 - 沒(méi)必要使用
NSZone
ARC 使用新修飾符
-
__strong
強(qiáng)引用,用來(lái)保證對(duì)象不會(huì)被釋放恋捆。 -
__weak
弱引用 釋放時(shí)會(huì)置為nil
-
__unsafe_unretained
弱引用 可能不安全照皆,因?yàn)獒尫艜r(shí)不置為nil
。 -
__autoreleasing
對(duì)象被注冊(cè)到autorelease pool
中方法在返回時(shí)自動(dòng)釋放沸停。
內(nèi)存泄漏
ARC
還是基于引用計(jì)數(shù)的管理機(jī)制所以依然會(huì)出現(xiàn)循環(huán)引用膜毁。
block使用中出現(xiàn)循環(huán)引用
- 常見(jiàn)的有情況在
block
使用中出現(xiàn)循環(huán)引用
// 情況一
self.myBlock = ^{
self.objc = ...;
};
// 情況二
Dog *dog = [[Dog alloc] init];
dog.myBlock = ^{
// do something
};
self.dog = dog;
- 解決方法
__weak typeof (self) weakSelf = self;
self.myBlock = ^{
weakSelf.objc = ...;
};
- 那么如果
block
內(nèi)使用了self
這個(gè)時(shí)候如果某一個(gè)時(shí)刻self
被釋放就會(huì)導(dǎo)致出現(xiàn)問(wèn)題。 - 解決方法
__weak typeof (self) weakSelf = self;
self.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
strongSelf.objc1 = ...;
strongSelf.objc2 = ...;
strongSelf.objc3 = ...;
};
- 使用
__weak
打破循環(huán)引用。__strong
用來(lái)避免在使用self
過(guò)程中self
被釋放瘟滨,__strong
在block
后會(huì)調(diào)用objc_release(obj)
釋放對(duì)象候醒。
id __strong obj = [[NSObject alloc] init];
// clang 編譯后
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
兩次調(diào)用objc_msgSend
并在變量作用域結(jié)束時(shí)調(diào)用objc_release
釋放對(duì)象,不會(huì)出現(xiàn)循環(huán)引用問(wèn)題杂瘸。
NSTimer循環(huán)引用
為什么NSTimer
會(huì)導(dǎo)致循環(huán)引用呢倒淫?
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
主要是因?yàn)?code>NSRunloop運(yùn)行循環(huán)保持了對(duì)
NSTimer
的強(qiáng)引用,并且NSTimer
的targer
也使用了強(qiáng)引用胧沫。來(lái)自文檔NSTimer
Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
舉個(gè)??:
@interface ViewController ()<viewControllerDelegate>
@property (strong, nonatomic) NSTimer *timer;
@end
- (void)viewDidLoad
{
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(onTimeOut:)
userInfo:nil
repeats:NO];
}
- 這里控制器強(qiáng)引用了
timer
昌简,而timer
也強(qiáng)引用了控制器,這個(gè)時(shí)候就是循環(huán)引用了,引用關(guān)系如下圖:
- 那么如果控制器對(duì)
timer
使用了weak
呢绒怨?
使用weak
是打破了循環(huán)引用,但是run loop
還是強(qiáng)引用著timer
,timer
又強(qiáng)引用著控制器纯赎,所以還是會(huì)導(dǎo)致內(nèi)存泄漏。引用關(guān)系如下圖:
如果我們把timer
加入主線程的runloop
,主線程中的runloop
生命周期只有主線程結(jié)束才會(huì)銷(xiāo)毀南蹂,所以我們不主動(dòng)調(diào)用[timer invalidate]
,runloop
會(huì)一直持有timer
犬金,timer
又持有控制器,那么就一直不會(huì)釋放控制器六剥。
- 解決方法:手動(dòng)調(diào)用
[timer invalidate]
來(lái)解除持有關(guān)系晚顷,釋放內(nèi)存×婆保可能會(huì)想到在dealloc
方法中來(lái)手動(dòng)調(diào)用该默,但是因?yàn)?code>timer持有控制器,所以控制器的dealloc
方法永遠(yuǎn)不會(huì)調(diào)用策彤,因?yàn)?code>dealloc是在控制器要被釋放前調(diào)用的栓袖。在Timer Programming Topics中有特別說(shuō)明。所以一般我們可以在下面這些方法中手動(dòng)調(diào)用[timer invalidate]
然后置為nil
:
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
A timer maintains a strong reference to its target. This means that as long as a timer remains valid, its target will not be deallocated. As a corollary, this means that it does not make sense for a timer’s target to try to invalidate the timer in its dealloc method—the dealloc method will not be invoked as long as the timer is valid.