iOS開(kāi)發(fā)之MRC(手動(dòng)內(nèi)存管理)詳解
前言:
在iOS中,使用引用計(jì)數(shù)來(lái)管理OC對(duì)象內(nèi)存
一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對(duì)象就會(huì)銷毀,釋放其占用的內(nèi)存空間。
調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1。
內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對(duì)象,在不需要這個(gè)對(duì)象時(shí),要調(diào)用release或者autorelease釋放它。
想擁有某個(gè)對(duì)象,就讓他的引用計(jì)數(shù)+1;不想再擁有某個(gè)對(duì)象,就讓他的引用計(jì)數(shù)-1。
一、 MRC 手動(dòng)管理內(nèi)存(Manual Reference Counting)
1、引用計(jì)數(shù)器
引用計(jì)數(shù)器:
一個(gè)整數(shù),表示為「對(duì)象被引用的次數(shù)」。系統(tǒng)需要根據(jù)對(duì)象的引用計(jì)數(shù)器來(lái)判斷對(duì)象是否需要被回收。
關(guān)于「引用計(jì)數(shù)器」,有以下幾個(gè)特點(diǎn):
- 每個(gè) OC 對(duì)象都有自己的引用計(jì)數(shù)器。
- 任何一個(gè)對(duì)象,剛創(chuàng)建的時(shí)候,初始的引用計(jì)數(shù)為 1。
- 即使用 alloc、new 或者 copy 創(chuàng)建一個(gè)對(duì)象時(shí),對(duì)象的引用計(jì)數(shù)器默認(rèn)就是 1。
- 當(dāng)沒(méi)有任何人使用這個(gè)對(duì)象時(shí),系統(tǒng)才會(huì)回收這個(gè)對(duì)象。也就是說(shuō):
- 當(dāng)對(duì)象的引用計(jì)數(shù)器為 0 時(shí),對(duì)象占用的內(nèi)存就會(huì)被系統(tǒng)回收。
- 如果對(duì)象的引用計(jì)數(shù)器不為 0 時(shí),那么在整個(gè)程序運(yùn)行過(guò)程,它占用的內(nèi)存就不可能被回收(除非整個(gè)程序已經(jīng)退出)。
2、引用計(jì)數(shù)器操作
- 為保證對(duì)象的存在,每當(dāng)創(chuàng)建引用到對(duì)象需要給對(duì)象發(fā)送一條 retain 消息,可以使引用計(jì)數(shù)器值 +1 ( retain 方法返回對(duì)象本身)。
- 當(dāng)不再需要對(duì)象時(shí),通過(guò)給對(duì)象發(fā)送一條 release 消息,可以使引用計(jì)數(shù)器值 -1。
- 給對(duì)象發(fā)送 retainCount 消息,可以獲得當(dāng)前的引用計(jì)數(shù)器值。
- 當(dāng)對(duì)象的引用計(jì)數(shù)為 0 時(shí),系統(tǒng)就知道這個(gè)對(duì)象不再需要使用了,所以可以釋放它的內(nèi)存,通過(guò)給對(duì)象發(fā)送 dealloc 消息發(fā)起這個(gè)過(guò)程。
- 需要注意的是:release 并不代表銷毀 / 回收對(duì)象,僅僅是將計(jì)數(shù)器 -1。
// 創(chuàng)建一個(gè)對(duì)象,默認(rèn)引用計(jì)數(shù)器是 1 RHPerson *person1 = [[RHPerson alloc] init]; NSLog(@"retainCount = %zd", [person1 retainCount]); // 只要給對(duì)象發(fā)送一條 retain 消息,引用計(jì)數(shù)器加1 [person1 retain]; NSLog(@"retainCount = %zd", [person1 retainCount]); // 只要給對(duì)象發(fā)送一條 release 消息,引用計(jì)數(shù)器減1 [person1 release]; NSLog(@"retainCount = %zd", [person1 retainCount]); // 當(dāng) retainCount 等于0時(shí),對(duì)象被銷毀 [person1 release]; NSLog(@"--------------");
2022-07-11 16:09:24.102850+0800 Interview01-內(nèi)存管理[8035:264221] retainCount = 1 2022-07-11 16:09:24.103083+0800 Interview01-內(nèi)存管理[8035:264221] retainCount = 2 2022-07-11 16:09:24.103126+0800 Interview01-內(nèi)存管理[8035:264221] retainCount = 1 2022-07-11 16:09:24.103231+0800 Interview01-內(nèi)存管理[8035:264221] -[RHPerson dealloc] 2022-07-11 16:09:24.103259+0800 Interview01-內(nèi)存管理[8035:264221] -------------- Program ended with exit code: 0
3、dealloc 方法
- 當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)器值為 0 時(shí),這個(gè)對(duì)象即將被銷毀,其占用的內(nèi)存被系統(tǒng)回收。
- 對(duì)象即將被銷毀時(shí)系統(tǒng)會(huì)自動(dòng)給對(duì)象發(fā)送一條 dealloc 消息(因此,從 dealloc 方法有沒(méi)有被調(diào)用,就可以判斷出對(duì)象是否被銷毀)
- dealloc 方法的重寫(xiě)(注意是在 MRC 中)
一般會(huì)重寫(xiě) dealloc 方法,在這里釋放相關(guān)資源,dealloc 就是對(duì)象的遺言
一旦重寫(xiě)了 dealloc 方法,就必須調(diào)用 [super dealloc],并且放在最后面調(diào)用
- (void)dealloc {
? ? NSLog(@"%s", __func__);
? ? [super dealloc];
}dealloc 使用注意:
不能直接調(diào)用 dealloc 方法。
一旦對(duì)象被回收了, 它占用的內(nèi)存就不再可用,堅(jiān)持使用會(huì)導(dǎo)致程序崩潰(野指針錯(cuò)誤)。
4、野指針和空指針
只要一個(gè)對(duì)象被釋放了,我們就稱這個(gè)對(duì)象為「僵尸對(duì)象(不能再使用的對(duì)象)」。
當(dāng)一個(gè)指針指向一個(gè)僵尸對(duì)象(不能再使用的對(duì)象),我們就稱這個(gè)指針為「野指針」。
只要給一個(gè)野指針發(fā)送消息就會(huì)報(bào)錯(cuò)(EXC_BAD_ACCESS 錯(cuò)誤)。
RHPerson *person1 = [[RHPerson alloc] init]; [person1 release]; [person1 release]; [person1 release];
為了避免給野指針發(fā)送消息會(huì)報(bào)錯(cuò),一般情況下,當(dāng)一個(gè)對(duì)象被釋放后我們會(huì)將這個(gè)對(duì)象的指針設(shè)置為空指針。
空指針:
沒(méi)有指向存儲(chǔ)空間的指針(里面存的是 nil, 也就是 0)。
給空指針發(fā)消息是沒(méi)有任何反應(yīng)的。
RHPerson *person1 = [[RHPerson alloc] init]; [person1 release]; person1 = nil; [person1 release];
二、內(nèi)存管理思想
1、單個(gè)對(duì)象內(nèi)存管理思想
思想一:自己創(chuàng)建的對(duì)象,自己持有,自己負(fù)責(zé)釋放
通過(guò) alloc、new、copy 或 mutableCopy 方法創(chuàng)建并持有對(duì)象。
當(dāng)自己持有的對(duì)象不再被需要時(shí),必須調(diào)用 release 或 autorelease 方法釋放對(duì)象。
id obj1 = [[NSObject alloc] init]; [obj1 release]; id obj2 = [NSObject new]; [obj2 release];
思想二:非自己創(chuàng)建的對(duì)象,自己也能持有
除了用上面方法(alloc / new / copy / mutableCopy 方法)所取得的的對(duì)象,因?yàn)榉亲约荷刹⒊钟?,所以自己不是該?duì)象的持有者。
通過(guò)調(diào)用 retain 方法,即便是非自己創(chuàng)建的對(duì)象,自己也能持有對(duì)象。
同樣當(dāng)自己持有的對(duì)象不再被需要時(shí),必須調(diào)用 release 方法來(lái)釋放對(duì)象。
id obj3 = [NSArray array]; [obj3 retain]; [obj3 release];
無(wú)論是否是自己創(chuàng)建的對(duì)象,自己都可以持有,并負(fù)責(zé)釋放。
計(jì)數(shù)器有加就有減。
曾經(jīng)讓對(duì)象的計(jì)數(shù)器 +1,就必須在最后讓對(duì)象計(jì)數(shù)器 -1。
2、多個(gè)對(duì)象內(nèi)存管理思想
多個(gè)對(duì)象之間往往是通過(guò) setter 方法產(chǎn)生聯(lián)系的,其內(nèi)存管理的方法也是在 setter 方法、dealloc 方法中實(shí)現(xiàn)的。所以只有了解了 setter 方法是如何實(shí)現(xiàn)的,我們才能了解到多個(gè)對(duì)象之間的內(nèi)存管理思想。
#import <Foundation/Foundation.h>
#import "RHRoom.h"
NS_ASSUME_NONNULL_BEGIN
@interface RHPerson : NSObject
{
? ? RHRoom *_room;
}
- (void)setRoom:(RHRoom *)room;
- (RHRoom *)room;
@end
NS_ASSUME_NONNULL_END
#import "RHPerson.h"
@implementation RHPerson
- (void)setRoom:(RHRoom *)room {
? ? if (_room != room) {
? ? ? ? [_room release];
? ? ? ? _room = [room retain];
? ? }
}
- (RHRoom *)room {
? ? return _room;
}
- (void)dealloc {
? ? [_room release];
? ? [super dealloc];
? ? NSLog(@"%s", __func__);
}
@end三、 @property 參數(shù)
在成員變量前加上 @property,系統(tǒng)就會(huì)自動(dòng)幫我們生成基本的 setter / getter 方法,但是不會(huì)生成內(nèi)存管理相關(guān)的代碼。
@property(nonatomic) int val;
同樣如果在 property 后邊加上 assign,系統(tǒng)也不會(huì)幫我們生成 setter 方法內(nèi)存管理的代碼,僅僅只會(huì)生成普通的 getter / setter 方法,默認(rèn)什么都不寫(xiě)就是 assign。
@property(nonatomic, assign) int val;
如果在 property 后邊加上 retain,系統(tǒng)就會(huì)自動(dòng)幫我們生成 getter / setter 方法內(nèi)存管理的代碼,但是仍需要我們自己重寫(xiě) dealloc 方法。
@property(nonatomic, retain) RHRoom *room;
四、自動(dòng)釋放池
1、自動(dòng)釋放池
當(dāng)我們不再使用一個(gè)對(duì)象的時(shí)候應(yīng)該將其空間釋放,但是有時(shí)候我們不知道何時(shí)應(yīng)該將其釋放。為了解決這個(gè)問(wèn)題,Objective-C 提供了 autorelease 方法。
autorelease 是一種支持引用計(jì)數(shù)的內(nèi)存管理方式,只要給對(duì)象發(fā)送一條 autorelease 消息,會(huì)將對(duì)象放到一個(gè)自動(dòng)釋放池中,當(dāng)自動(dòng)釋放池被銷毀時(shí),會(huì)對(duì)池子里面的「所有對(duì)象」做一次 release 操作。
注意:這里只是發(fā)送 release 消息,如果當(dāng)時(shí)的引用計(jì)數(shù)(reference-counted)依然不為 0,則該對(duì)象依然不會(huì)被釋放。
autorelease 方法會(huì)返回對(duì)象本身,且調(diào)用完 autorelease 方法后,對(duì)象的計(jì)數(shù)器不變。
NSObject *obj = [NSObject new]; [obj autorelease]; NSLog(@"obj.retainCount = %zd", obj.retainCount);
2、使用 autorelease 有什么好處呢?
不用再關(guān)心對(duì)象釋放的時(shí)間
不用再關(guān)心什么時(shí)候調(diào)用release
3、autorelease 的原理實(shí)質(zhì)上是什么?
? autorelease 實(shí)際上只是把對(duì) release 的調(diào)用延遲了,對(duì)于每一個(gè) autorelease,系統(tǒng)只是把該對(duì)象放入了當(dāng)前的 autorelease pool 中,當(dāng)該 pool 被釋放時(shí),該 pool 中的所有對(duì)象會(huì)被調(diào)用 release 方法。
4、autorelease 的創(chuàng)建方法
// 第一種方式:使用 NSAutoreleasePool 創(chuàng)建
// 創(chuàng)建自動(dòng)釋放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 銷毀自動(dòng)釋放池
[pool release];
// 第二種方式:使用 @autoreleasepool 創(chuàng)建
@autoreleasepool {
// 開(kāi)始代表創(chuàng)建自動(dòng)釋放池
// 結(jié)束代表銷毀自動(dòng)釋放池
}
5、autorelease 的使用方法
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
RHPerson *p = [[[RHPerson alloc] init] autorelease];
[pool release];
@autoreleasepool {
// 開(kāi)始代表創(chuàng)建自動(dòng)釋放池
RHPerson *p = [[[RHPerson alloc] init] autorelease];
// 結(jié)束代表銷毀自動(dòng)釋放池
}
6、autorelease 的注意事項(xiàng)
并不是放到自動(dòng)釋放池代碼中,都會(huì)自動(dòng)加入到自動(dòng)釋放池
@autoreleasepool {
// 因?yàn)闆](méi)有調(diào)用 autorelease,所以沒(méi)有加入到自動(dòng)釋放池中
RHPerson *p = [[RHPerson alloc] init];
// 結(jié)束代表銷毀自動(dòng)釋放池
}在自動(dòng)釋放池的外部發(fā)送 autorelease 不會(huì)被加入到自動(dòng)釋放池中
autorelease 是一個(gè)方法,只有在自動(dòng)釋放池中調(diào)用才有效。
@autoreleasepool {
}
// 沒(méi)有與之對(duì)應(yīng)的自動(dòng)釋放池, 只有在自動(dòng)釋放池中調(diào)用autorelease才會(huì)放到釋放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
?
// 正確寫(xiě)法
@autoreleasepool {
? ? Person *p = [[[Person alloc] init] autorelease];
?}
?
// 正確寫(xiě)法
Person *p = [[Person alloc] init];
@autoreleasepool {
? ? [p autorelease];
}
7、自動(dòng)釋放池的嵌套使用
自動(dòng)釋放池是以棧的形式存在。
由于棧只有一個(gè)入口,所以調(diào)用 autorelease 會(huì)將對(duì)象放到棧頂?shù)淖詣?dòng)釋放池。
棧頂就是離調(diào)用 autorelease 方法最近的自動(dòng)釋放池。
@autoreleasepool { // 棧底自動(dòng)釋放池
? ? @autoreleasepool {
? ? ? ? @autoreleasepool { // 棧頂自動(dòng)釋放池
? ? ? ? ? ? Person *p = [[[Person alloc] init] autorelease];
? ? ? ? }
? ? ? ? Person *p = [[[Person alloc] init] autorelease];
? ? }
}
自動(dòng)釋放池中不適宜放占用內(nèi)存比較大的對(duì)象。
盡量避免對(duì)大內(nèi)存使用該方法,對(duì)于這種延遲釋放機(jī)制,還是盡量少用。
不要把大量循環(huán)操作放到同一個(gè) @autoreleasepool 之間,這樣會(huì)造成內(nèi)存峰值的上升。
// 內(nèi)存暴漲
@autoreleasepool {
? ? for (int i = 0; i < 99999; ++i) {
? ? ? ? Person *p = [[[Person alloc] init] autorelease];
? ? }
}
// 內(nèi)存不會(huì)暴漲
for (int i = 0; i < 99999; ++i) {
? ? @autoreleasepool {
? ? ? ? Person *p = [[[Person alloc] init] autorelease];
? ? }
}8、autorelease 錯(cuò)誤用法
不要連續(xù)調(diào)用 autorelease。
@autoreleasepool {
?// 錯(cuò)誤寫(xiě)法, 過(guò)度釋放
? ? Person *p = [[[[Person alloc] init] autorelease] autorelease];
?}調(diào)用 autorelease 后又調(diào)用 release(錯(cuò)誤)。
@autoreleasepool {
? ? Person *p = [[[Person alloc] init] autorelease];
? ? [p release]; // 錯(cuò)誤寫(xiě)法, 過(guò)度釋放
}五、 MRC 中避免循環(huán)引用
定義兩個(gè)類 Person 類和 Dog 類
Person 類:
#import <Foundation/Foundation.h> @class Dog; ? @interface Person : NSObject @property(nonatomic, retain)Dog *dog; @end
Dog 類:
#import <Foundation/Foundation.h> @class Person; ? @interface Dog : NSObject @property(nonatomic, retain)Person *owner; @end
執(zhí)行以下代碼:
int main(int argc, const char * argv[]) {
? ? Person *p = [Person new];
? ? Dog *d = [Dog new];
?
? ? p.dog = d; // retain
? ? d.owner = p; // retain ?assign
?
? ? [p release];
? ? [d release];
?
? ? return 0;
}就會(huì)出現(xiàn) A 對(duì)象要擁有 B 對(duì)象,而 B 對(duì)應(yīng)又要擁有 A 對(duì)象,此時(shí)會(huì)形成循環(huán) retain,導(dǎo)致 A 對(duì)象和 B 對(duì)象永遠(yuǎn)無(wú)法釋放。
那么如何解決這個(gè)問(wèn)題呢?
不要讓 A retain B,B retain A。
讓其中一方不要做 retain 操作即可。
當(dāng)兩端互相引用時(shí),應(yīng)該一端用 retain,一端用 assign。
到此這篇關(guān)于 iOS開(kāi)發(fā)之MRC(手動(dòng)內(nèi)存管理)詳解的文章就介紹到這了,更多相關(guān)MRC 手動(dòng)內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
iOS開(kāi)發(fā)xconfig和script腳本使用詳解
這篇文章主要為大家介紹了iOS開(kāi)發(fā)xconfig和script腳本使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
iOS 仿百度外賣-首頁(yè)重力感應(yīng)的實(shí)例
這篇文章主要介紹了iOS 仿百度外賣-首頁(yè)重力感應(yīng)的實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01
IOS UITableView和NavigationBar的常用設(shè)置詳解
這篇文章主要介紹了IOS UITableView和NavigationBar的常用設(shè)置詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
ios開(kāi)發(fā)navigationController pushViewController 方式多次跳轉(zhuǎn)返回到最上層返回到
這篇文章主要介紹了ios開(kāi)發(fā)navigationController pushViewController 方式多次跳轉(zhuǎn)返回到最上層返回到指定的某一層的實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
iOS開(kāi)發(fā)中TabBar再次點(diǎn)擊實(shí)現(xiàn)刷新效果
這篇文章主要介紹了iOS開(kāi)發(fā)中TabBar再次點(diǎn)擊實(shí)現(xiàn)刷新效果,實(shí)現(xiàn)方法也很簡(jiǎn)單,需要的朋友可以參考下2018-04-04
iOS使用核心動(dòng)畫(huà)和粒子發(fā)射器實(shí)現(xiàn)點(diǎn)贊按鈕的方法
這篇文章主要給大家介紹了iOS如何使用核心動(dòng)畫(huà)和粒子發(fā)射器實(shí)現(xiàn)點(diǎn)贊按鈕的方法,文中給出了詳細(xì)的示例代碼,相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考借鑒,有需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2016-12-12

