一, iOS的內(nèi)存管理規(guī)則
1 基本原則
移動設(shè)備的內(nèi)存極其有限,每個app所能占用的內(nèi)存是有限制的
當(dāng)app所占用的內(nèi)存較多時,系統(tǒng)會發(fā)出內(nèi)存警告,這時得回收一些不需要再使用的內(nèi)存空間。比如回收一些不需要使用的對象、變量等
管理范圍:任何繼承了NSObject的對象,對其他基本數(shù)據(jù)類型(int、char、float、double、struct、enum等)無效
QQ堂開房間原理:只要房間還有人在用,就不會解散
只要還有人在用某個對象,那么這個對象就不會被回收
只要你想用這個對象,就讓對象的計數(shù)器+1
當(dāng)你不再使用這個對象時,就讓對象的計數(shù)器-1
- 1, 你想使用(占用)某個對象,就應(yīng)該讓對象的計數(shù)器+1(讓對象做一次retain操作)
- 2, 你不想再使用(占用)某個對象,就應(yīng)該讓對象的計數(shù)器-1(讓對象做一次release)
- 3, 誰alloc,誰release
如果你通過alloc、new或[mutable]copy來創(chuàng)建一個對象,那么你必須調(diào)用release或autorelease
換句話說,不是你創(chuàng)建的,就不用你去[auto]release
- 4, 誰retain,誰release
只要你調(diào)用了retain,無論這個對象是如何生成的,你都要調(diào)用release
- 5, 總結(jié)
有始有終,有加就有減
曾經(jīng)讓對象的計數(shù)器+1,就必須在最后讓對象計數(shù)器-1
所以,一般需要在三個地方進行release
- 在main或其他函數(shù)創(chuàng)建對象后.
- 在dealloc方法內(nèi)
- 在set方法內(nèi),進行if判斷
2 ARC
ARC是編譯器(時)特性,而不是運行時特性,更不是垃圾回收器
2.1、 自動釋放池
autorelease 其實并不是自動釋放,而是把對release的調(diào)用延遲了
autorelease 返回的是對象本身.
// autorelease方法會返回對象本身
// 調(diào)用完autorelease方法后,對象的計數(shù)器不變
// autorelease會將對象放到一個自動釋放池中
// 當(dāng)自動釋放池被銷毀時,會對池子里面的所有對象做一次release操作
1、 autorelease的基本用法
- 給某個對象發(fā)送一條autorelease消息時,就會將這個對象加到一個* 自動釋放池中
- 當(dāng)自動釋放池銷毀時,會給池子里面的所有對象發(fā)送一條release消息
- 調(diào)用autorelease方法時并不會改變對象的計數(shù)器,
- 調(diào)用autorelease方法并且會 會返回對象本身autorelease實際上只是把對release的調(diào)用延遲了,對于每一次autorelease,系統(tǒng)只是把該對象放入了當(dāng)前的autorelease pool中,當(dāng)該pool被釋放時,該pool中的所有對象會被調(diào)用Release
2、autorelease的好處
1> 不用再關(guān)心對象釋放的時間
2> 不用再關(guān)心什么時候調(diào)用release
3、autorelease的使用注意
1> 占用內(nèi)存較大的對象不要隨便使用 autorelease
2> 占用內(nèi)存較小的對象使用autorelease,沒有太大影響
4、autorelease 應(yīng)用的場景
1,開發(fā)中經(jīng)常會提供一些類方法,快速創(chuàng)建一個已經(jīng)autorelease過的對象
一般可以為類添加一個快速創(chuàng)建對象的類方法
創(chuàng)建一個類方法,直接返回一個扔進釋放池里面的對象
聲明:
+ (id)person; 一般是和類同名的小寫.
實現(xiàn):
+ (id)person{
創(chuàng)建對象時不要直接用類名,一般用self
//因為這個self代表類 解決了子類調(diào)用父類的問題,滿足子類需求 ,避免了重寫
return [[[self alloc] init] autorelease];
}
調(diào)用: 創(chuàng)建對象
外界調(diào)用[Person person]時,根本不用考慮在什么時候釋放返回的Person對象
Person *p2 = [Person person];
以前的是: Person *p2 = [[[Person alloc] init] autorelease];
也可以擴充方法
+ (id)personWithAge:(int)age{
Person *p = [self person];
p.age = age;
return p;
}
2、規(guī)律
系統(tǒng)自帶的方法里面沒有包含alloc、new、copy,說明返回的對象都是autorelease的
一般來說,除了alloc、new或copy之外的方法創(chuàng)建的對象都被聲明了autorelease
比如下面的對象都已經(jīng)是autorelease的,不需要再release
NSNumber *n = [NSNumber numberWithInt:100];
NSString *s = [NSString stringWithFormat:@"jack"];
NSString *s2 = @"rose”;
2.2. ARC的修飾符
ARC提供四種修飾符,分別是strong, weak, autoreleasing, unsafe_unretained
__strong:強引用,持有所指向?qū)ο蟮乃袡?quán),無修飾符情況下的默認值。如需強制釋放,可置nil。
比如我們常用的定時器
NSTimer * timer = [NSTimer timerWith...];
相當(dāng)于
NSTimer * __strong timer = [NSTimer timerWith...];
當(dāng)不需要使用時,強制銷毀定時器
[timer invalidate];
timer = nil;
__weak:弱引用,不持有所指向?qū)ο蟮乃袡?quán),引用指向的對象內(nèi)存被回收之后,引用本身會置nil,避免野指針。
比如避免循環(huán)引用的弱引用聲明:
__weak __typeof(self) weakSelf = self;
__autoreleasing:自動釋放對象的引用,一般用于傳遞參數(shù)
比如一個讀取數(shù)據(jù)的方法
- (void)loadData:(NSError **)error;
當(dāng)你調(diào)用時會發(fā)現(xiàn)這樣的提示
NSError * error; [dataTool loadData:(NSError *__autoreleasing *)]
這是編譯器自動幫我們插入以下代碼
NSError * error;
NSError * __autoreleasing tmpErr = error;
[dataTool loadData:&tmpErr];
__unsafe_unretained:為兼容iOS5以下版本的產(chǎn)物,可以理解成MRC下的weak,現(xiàn)在基本用不到,這里不作描述。
2.3. ARC的開啟與關(guān)閉
設(shè)置方法:
Targets -->Build Phases -->Compile Sources
如果你的工程是開啟ARC的, 那就需要對某些文件禁用ARC, (-fno-objc-arc)
如果你的工程是關(guān)閉ARC的, 那就需要對某些文件開啟ARC.(-fobjc-arc)
3. @property屬性的內(nèi)存管理
1、控制 set方法內(nèi)存管理相關(guān)的參數(shù)
- retain : release舊值,retain新值(適用于OC對象類型)
- assign : 直接賦值(默認,適用于非OC對象類型)
- copy : release舊值,copy新值(一般用于 NSString * )
2、控制 是否要生成set方法
- readwrite : 同時生成setter和getter的聲明、實現(xiàn)(默認)
- readonly : 只會生成getter的聲明、實現(xiàn)
3、控制 多線程管理
- nonatomic : 性能高 (一般就用這個) 不加鎖
- atomic : 性能低(默認) 加鎖
4、控制 setter和getter方法的名稱
- setter : 決定了set方法的名稱,一定要有個冒號 :
- getter : 決定了get方法的名稱(一般用在BOOL類型)
// 返回BOOL類型的方法名一般以is開頭
@property (getter = isRich, setter = setIsRich:) BOOL rich;
@property (nonatomic, assign, readwrite) int weight;
4. block的內(nèi)存管理
Block的類型與內(nèi)存管理
根據(jù)Block在內(nèi)存中的位置分為三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:類似函數(shù),位于text段;
NSStackBlock:位于棧內(nèi)存,函數(shù)返回后Block將無效;
NSMallocBlock:位于堆內(nèi)存。
- 無論當(dāng)前環(huán)境是ARC還是MRC,只要block沒有訪問外部變量,block始終在全局區(qū)
- MRC情況下
1.block如果訪問外部變量,block在棧里
2.不能對block使用retain,否則不能保存在堆里
3.只有使用copy,才能放到堆里 - ARC情況下
1.block如果訪問外部變量,block在堆里
2.block可以使用copy和strong,并且block是一個對象
block的循環(huán)引用
如果要在block中直接使用外部強指針會發(fā)生錯誤,使用以下代碼在block外部實現(xiàn)可以解決
__weak typeof(self) weakSelf = self;
但是如果在block內(nèi)部使用延時操作還使用弱指針的話會取不到該弱指針,需要在block內(nèi)部再將弱指針強引用一下
__strong typeof(self) strongSelf = weakSelf;
iOS中使用block必須自己管理內(nèi)存,錯誤的內(nèi)存管理將導(dǎo)致循環(huán)引用等內(nèi)存泄漏問題,這里主要說明在ARC下block聲明和使用的時候需要注意的兩點: 1)如果你使用@property去聲明一個block的時候,一般使用copy來進行修飾(當(dāng)然也可以不寫,編譯器自動進行copy操作),盡量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);
2)block會對內(nèi)部使用的對象進行強引用,因此在使用的時候應(yīng)該確定不會引起循環(huán)引用,當(dāng)然保險的做法就是添加弱引用標記。
__weak typeof(self) weakSelf = self;
有興趣的讀者可以深入了解:
1、block的內(nèi)部實現(xiàn)原理是什么? 2、從內(nèi)存位置來看block有幾種類型?它們的內(nèi)存管理方式各是怎樣的? 3、對于不同類型的外部變量,block的內(nèi)存管理都是怎樣的?
5. 內(nèi)存管理代碼規(guī)范
- 1, 函數(shù)或方法 里面只要調(diào)用了
alloc,必須有release(autorelease)
對象不是通過alloc產(chǎn)生的,就不需要release
NSString *s = @"Jack";
stu.name = s;
- 2, set方法的代碼規(guī)范
1> 基本數(shù)據(jù)類型:直接復(fù)制
- (void)setAge:(int)age {
_age = age;
}
2> OC對象類型
- (void)setCar:(Car *)car {
// 1.先判斷是不是新傳進來對象
if ( car != _car ) {
// 2.對舊對象做一次release
[_car release];
// 3.對新對象做一次retain
_car = [carretain];
}
}
- 3,
dealloc方法的代碼規(guī)范 不要直接調(diào)用dealloc
1> 一定要[super dealloc],而且放到最后面
2> 對self(當(dāng)前)所擁有的其他對象做一次release
- (void)dealloc {
[_car release];
[super dealloc];
}
</br>
二, 經(jīng)典內(nèi)存泄漏
1 僵尸對象和野指針
</br>
2 循環(huán)引用
</br>
3 循環(huán)中對象占用內(nèi)存大
</br>
4 無限循環(huán)
</br>
3, 項目中的注意點
- 1, 代理 nil
- 2, 通知 移除
- 3, kvo 移除
- 4, block 循環(huán)引用 __weak mrc __block
- 5, 對象的循環(huán)引用 一端 assign 一端 strong