Objective-C之我所理解的內(nèi)存管理

前言

伴隨著iOS5的發(fā)布,在Xcode4.2中加入了一個(gè)振奮人心的新特性。ARC,開啟這個(gè)特性后,幫我們省去了許多內(nèi)存管理的代碼,讓我們把更多的精力集中到功能的實(shí)現(xiàn)上。雖然ARC如此的完美,但作為iOS Developer,學(xué)習(xí)MRC,同樣的重要。


為什么要學(xué)內(nèi)存管理 ?

  • iOS應(yīng)用程序出現(xiàn)Crash(閃退),90%的原因是因?yàn)閮?nèi)存問題
  • 在iPhone 6s以前,大多數(shù)iPhone的運(yùn)行內(nèi)存都是1G,每個(gè)應(yīng)用程序啟動(dòng)后分配的內(nèi)存空間極其有限,當(dāng)應(yīng)用程序運(yùn)行過程中所占用的內(nèi)存較大時(shí),便會收到系統(tǒng)給出的內(nèi)存警告,如果應(yīng)用程序所占用的內(nèi)存超過限制時(shí),便會被系統(tǒng)強(qiáng)制關(guān)閉,所以我們需要對應(yīng)用程序進(jìn)行內(nèi)存管理,一個(gè)好的程序程序也應(yīng)該盡可能少地占用內(nèi)存

內(nèi)存管理的兩種方式

  • MRC
    • Mannul Reference Counting
    • 手動(dòng)引用計(jì)數(shù)
  • ARC
    • Automatic Reference Counting
    • 自動(dòng)引用計(jì)數(shù)
  • 從Xcode4.2過后,系統(tǒng)默認(rèn)就開啟了ARC,但我們可以手動(dòng)選擇使用MRC

哪些變量需要做內(nèi)存管理

  • 通常由程序員自己創(chuàng)建的對象(繼承于NSObject)需要做內(nèi)存管理,因?yàn)樗麄兇鎯υ诙褍?nèi)存中
  • 而基本數(shù)據(jù)類型,比如int, float,double,char,struct等不需要做內(nèi)存管理,因?yàn)樗鼈兇鎯υ跅?nèi)存中,棧內(nèi)存中的變量會自己管理自己。

MRC

  • Mannul Reference Counting
  • 手動(dòng)引用計(jì)數(shù)
  • 在iOS5以前,程序員普遍使用MRC方式來管理內(nèi)存,程序員需要自己添加retain、release和autorelease等內(nèi)存管理代碼來跟蹤自己所擁有的對象以明確地管理對象的內(nèi)存,在需要使用該對象的時(shí)候保證該對象一直存在于內(nèi)存中,在不需要使用該對象的時(shí)候保證該對象所占用的內(nèi)存被系統(tǒng)正常回收
  • 為了讓系統(tǒng)知道何時(shí)需要將某個(gè)對象所占用的內(nèi)存清理掉,系統(tǒng)引入了引用計(jì)數(shù)器的概念

引用計(jì)數(shù)器

  • 概念
    • 系統(tǒng)為每個(gè)OC對象內(nèi)部都分配了4個(gè)字節(jié)的存儲空間存放自己的引用計(jì)數(shù)器,引用計(jì)數(shù)器是一個(gè)整數(shù),表示“對象被引用的次數(shù)”,當(dāng)對象的引用計(jì)數(shù)器為0時(shí),對象所占用的內(nèi)存空間就會被系統(tǒng)自動(dòng)回收,當(dāng)對象的引用計(jì)數(shù)器不為0時(shí),在程序運(yùn)行過程中所占用的內(nèi)存會一直存在,直到整個(gè)程序退出時(shí)由OS自動(dòng)釋放
  • 操作應(yīng)用計(jì)數(shù)器
    • 當(dāng)使用alloc、new、copy、mutableCopy創(chuàng)建一個(gè)新對象時(shí),該新對象的引用計(jì)數(shù)器為1
    • 當(dāng)給對象發(fā)送一條retain消息時(shí),對象的引用計(jì)數(shù)器+1(方法返回對象本身)
    • 當(dāng)給對象發(fā)送一條release消息時(shí)對象的引用計(jì)數(shù)器-1(方法無返回值)
    • 當(dāng)給對象發(fā)送一條retainCount消息時(shí),返回對象的當(dāng)前引用計(jì)數(shù)器(不要以該數(shù)據(jù)來判斷對象是否被釋放)

  • 例子
Person *p = [[Person alloc] init]; // 使用alloc創(chuàng)建一個(gè)新對象,對象引用計(jì)數(shù)器 = 1
[p retain]; // 給對象發(fā)送一條retain消息,對象引用計(jì)數(shù)器 + 1 = 2
[p release]; // 給對象發(fā)送一條release消息,對象引用計(jì)數(shù)器 - 1 = 1
[p release]; // 給對象發(fā)送一條release消息,對象引用計(jì)數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
  • 注意
    • 當(dāng)引用計(jì)數(shù)器為0時(shí),會自動(dòng)調(diào)用dealloc方法

dealloc方法

  • 當(dāng)系統(tǒng)回收對象的內(nèi)存時(shí),系統(tǒng)會自動(dòng)給該對象發(fā)送一條dealloc消息,我們一般會重寫dealloc方法,在這里給當(dāng)前對象所擁有的資源(包括實(shí)例變量)發(fā)送一條release消息(基本數(shù)據(jù)類型不用),保證自身所擁有的資源也可以正常釋放(因?yàn)樵谑褂迷撡Y源的時(shí)候,采用retain獲取了該資源的所有權(quán),在自身釋放的同時(shí),也應(yīng)該放棄對該資源的所有權(quán))

- (void)dealloc
{
    NSLog(@"Person dealloc");

    // release對象所擁有資源
    [_room release];
    // 設(shè)置為nil可以避免野指針錯(cuò)誤(其實(shí)可以不設(shè)置,只是寫了顯得有逼格)
    _room = nil;

    [super dealloc];
}
  • 注1: 不要直接調(diào)用對象的dealloc方法
  • 注2: 重寫dealloc方法時(shí),一定要調(diào)用[super dealloc]方法,且放在代碼的最后
  • 注3: 當(dāng)應(yīng)用程序被關(guān)掉,dealloc方法不一定會被調(diào)用,因?yàn)橛上到y(tǒng)OS直接來釋放內(nèi)存比調(diào)用dealloc釋放內(nèi)存效率得多
  • 注4: 不要在dealloc方法中管理稀缺資源(比如網(wǎng)絡(luò)連接,文件資源,DOS命令等),因?yàn)閐ealloc并不一定都是立即調(diào)用,有可能會延遲調(diào)用,也可能根本不會被調(diào)用

僵尸對象、野指針與空指針

  • 僵尸對象
    • 所占用的內(nèi)存已經(jīng)被回收的對象,僵尸對象不能再使用
  • 野指針
    • 指向僵尸對象的指針,給野指針發(fā)送消息會報(bào)錯(cuò)EXC_BAD_ACCESS錯(cuò)誤:訪問了一塊已經(jīng)被回收的內(nèi)存
  • 空指針
    • 沒有指向任何對象的指針(存儲的東西是nil,NULL,0),給空指針發(fā)送消息不會報(bào)錯(cuò),系統(tǒng)什么也不會做,所以在對象被釋放時(shí)將指針設(shè)置為nil可以避免野指針錯(cuò)誤

注: 默認(rèn)情況下,Xcode是不會監(jiān)聽僵尸對象的,所以需要我們自己手動(dòng)開啟,開啟監(jiān)聽僵尸對象步驟為: Edit Scheme ->; Run ->; Diagnostics ->; Objective-C的Enable Zombie Objects打鉤,這樣便可以在因?yàn)榻┦瑢ο髨?bào)錯(cuò)的時(shí)候給出更多錯(cuò)誤信息

  • 例子
 // 引用計(jì)數(shù)器 = 1
Person *p = [[Person alloc] init];

 // 引用計(jì)數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
[p release];

// 這句給野指針發(fā)送消息,會報(bào)野指針錯(cuò)誤,開啟監(jiān)聽僵尸對象會給出錯(cuò)誤信息
// -[Person release]: message sent to deallocated instance 0x100206fd0
[p release]; 
  • 如何避免野指針問題
    • 如果在第一次給對象發(fā)送release消息后,立刻將指針置空,便不會出現(xiàn)野指針錯(cuò)誤,因?yàn)榻o空指針發(fā)送消息不會報(bào)錯(cuò),系統(tǒng)什么也不會做,所以在對象被釋放時(shí)將指針設(shè)置為nil可以避免野指針錯(cuò)誤

自動(dòng)釋放池

自動(dòng)釋放池提供了延遲放棄一個(gè)對象的所有權(quán)的機(jī)制,比如想要在一個(gè)方法中返回一個(gè)對象,如果先使用release放棄了該對象的所有權(quán),那么return返回的對象便是一個(gè)僵尸對象,如果先進(jìn)行return返回,那么便無法放棄該對象的所有權(quán),導(dǎo)致了內(nèi)存泄漏


  • 創(chuàng)建
  • iOS5.0之前創(chuàng)建自動(dòng)釋放池方法(現(xiàn)在也可使用)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];
  • iOS5.0之后創(chuàng)建自動(dòng)釋放池方法
@autoreleasepool
{
    // do something...
}
  • autorelease方法
    • 是一種支持引用計(jì)數(shù)的內(nèi)存管理方式,只要在自動(dòng)釋放池中給對象發(fā)送一條autorelease消息,就會將對象放到自動(dòng)釋放池中,當(dāng)自動(dòng)釋放池被銷毀時(shí),會對池中的所有對象發(fā)送一條release消息
    • autorelease方法會返回對象本身
    • autorelease方法不會修改對象的引用計(jì)數(shù)器
    • autorelease方法可以讓開發(fā)者不用實(shí)時(shí)關(guān)心什么時(shí)候發(fā)送release消息

注1: 自動(dòng)釋放池被銷毀時(shí),只是給池中所有對象發(fā)送一條release消息,不代表對象一定會被釋放

注2: 對象在自動(dòng)釋放池中每收到一條autorelease消息,在自動(dòng)釋放池被銷毀時(shí),對象都會收到一次release消息


  • autorelease方法使用注意事項(xiàng)
    • 一定要在自動(dòng)釋放池中調(diào)用autorelease方法,才會將對象放入自動(dòng)釋放池中
    • 即使在自動(dòng)釋放池內(nèi)創(chuàng)建對象,只要不調(diào)用了autorelease方法,就不會將對象放入自動(dòng)釋放池中
    • 即使在自動(dòng)釋放池外創(chuàng)建對象,只要在自動(dòng)釋放池中調(diào)用了autorelease方法,就會將對象放入自動(dòng)釋放池中
    • 一個(gè)程序中可以創(chuàng)建N個(gè)自動(dòng)釋放池,且自動(dòng)釋放池可以嵌套,這些自動(dòng)釋放池以棧結(jié)構(gòu)存在(先進(jìn)后出),當(dāng)一個(gè)對象調(diào)用autorelease方法時(shí),會將這個(gè)對象放到棧頂?shù)淖詣?dòng)釋放池中
    • autorelease不能精準(zhǔn)地釋放內(nèi)存(延遲釋放),因?yàn)橐獙⒊刂械乃袃?nèi)容都執(zhí)行完才會釋放自動(dòng)釋放池,所以占用內(nèi)存比較大的東西還是使用release為宜

ARC

  • Automatic Reference Counting

  • 自動(dòng)引用計(jì)數(shù)

  • 它是iOS4引入的一項(xiàng)新技術(shù)(從iOS5開始支持弱引用),其使用與MRC相同的內(nèi)存管理規(guī)則來管理內(nèi)存,不過編譯器會在編譯階段自動(dòng)地在適當(dāng)?shù)奈恢貌迦雛etain、release和autorelease等內(nèi)存管理代碼來管理內(nèi)存(屬于編譯器特性,不是運(yùn)行時(shí)特性),不再需要程序人員手動(dòng)管理.官方強(qiáng)烈建議使用ARC方式來管理內(nèi)存


注: OC中的ARC和Java中的垃圾回收機(jī)制不一樣,Java中的垃圾回收是系統(tǒng)做的,而OC中的ARC是編譯器做的


  • MRC和ARC示例
// MRC
@interface Person : NSObject

@property (retain) NSNumber *number;

@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");

    [_number release];

    [super dealloc];
}

@end

Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;

[number release];
[person release];
// perosn和number正常被釋放
// ARC
@interface Person : NSObject

@property (strong) NSNumber *number;

@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end

Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;

// perosn和number出了作用域正常被釋放
  • ARC與MRC的混合開發(fā)

    • 如果想在ARC項(xiàng)目中使用MRC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fno-objc-arc
    • 如果想在MRC項(xiàng)目中使用ARC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fobjc-arc
  • ARC引入的新規(guī)則

    • 為了使ARC能夠正常工作,在ARC中引入了一些區(qū)別于當(dāng)前編譯模式的新的規(guī)則,如果你違反了這些規(guī)則,在編譯階段編譯器會給出一個(gè)警告
    • 不能實(shí)現(xiàn)或者調(diào)用retain、release、autorelease和retainCount方法,甚至不能使用@selector(retain)、@selector(release)等方式調(diào)用
    • 不能調(diào)用dealloc方法
      • 可以實(shí)現(xiàn)dealloc方法,用于釋放除了實(shí)例變量以外的其他資源
      • 不需要在這里釋放實(shí)例變量(實(shí)際上也不能在這里給實(shí)例變量發(fā)送release消息)
      • 可以在這里調(diào)用[systemClassInstance setDelegate:nil],以便處理不是用ARC編譯的systemClass(在MRC下delegate使用assign修飾,如果自身被釋放,delegate會變成野指針,所以需要在dealloc中將其置空;在ARC下delegate使用weak修飾,如果自身被釋放,delegate會自動(dòng)置空)
      • 不需要調(diào)用[super dealloc],編譯器會自動(dòng)調(diào)用
  • 不能使用NSAutoreleasePool來創(chuàng)建自動(dòng)釋放池,而是需要使用@autoreleasepool來代替
    為了與MRC之間進(jìn)行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個(gè)新的getter名稱

  • 為了與MRC之間進(jìn)行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個(gè)新的getter名稱

// 錯(cuò)誤
@property NSString *newTitle;

// 正確
@property (getter=theNewTitle) NSString *newTitle;

ARC引入的新特性

兩個(gè)屬性修飾符: strong和weak

  • 在ARC中新增了兩個(gè)屬性修飾符: strong和weak,其中strong是默認(rèn)修飾符,下面介紹一下這兩個(gè)屬性修飾符與retain和assign的區(qū)別
// 下面這句對于strong的示例,與此同義: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;

// 下面這句對于weak的示例,與此相似: @property(assign) MyClass *myObject;
// 使用assign修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成野指針;使用weak修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成空指針
@property(weak) MyClass *myObject;
  • 針對于ARC中屬性修飾符的使用,要進(jìn)行如下變化
    • strong用于OC對象,相當(dāng)于MRC中的retain
    • weak用于OC對象,相當(dāng)于MRC中的assign
    • assign用于基本數(shù)據(jù)類型,相當(dāng)于MRC中的assign

注: 其實(shí)就是將MRC中的assign分成了兩個(gè)部分,分別用于修飾OC對象與基本數(shù)據(jù)類型


四個(gè)變量修飾符

在ARC中新增了四個(gè)變量修飾符: 雙下劃線strong、雙下劃線weak、雙下劃線unsafe_unretained和雙下劃線autoreleasing,其中雙下劃線strong是默認(rèn)修飾符,下面介紹一下這四個(gè)變量修飾符

  • 雙下劃線strong: 強(qiáng)引用,只要有強(qiáng)指針指向該變量,該變量便會一直存在
  • 雙下劃線weak: 弱引用,只要沒有強(qiáng)指針指向該變量,該變量便會被置空(即設(shè)置為nil)
  • 雙下劃線unsafe_unretained: 不安全的弱引用,只要沒有強(qiáng)指針指向該變量,該變量不會被置空(即設(shè)置為nil),而會變成野指針
  • 雙下劃線autoreleasing: 用于標(biāo)示自動(dòng)釋放的變量

  • 官方提醒,在為變量添加修飾符時(shí),最正確的方式如下
// 規(guī)則
ClassName * qualifier variableName;

// 正確示例
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;

// 錯(cuò)誤示例(雖然錯(cuò)誤,但是編譯器會默認(rèn)為正確,官方說法為"forgiven")
__weak MyClass * myWeakReference;
__unsafe_unretained MyClass * myUnsafeReference;

注: 在直接使用__weak修飾變量指向一個(gè)剛創(chuàng)建的對象時(shí),需要注意對象剛剛創(chuàng)建出來就會釋放的情況

NSString * __weak string = [[NSString alloc] initWithFormat:@"loly"];
// 因?yàn)闆]有強(qiáng)指針指向該對象,該對象會立即被釋放

最后

溫故而知新。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS內(nèi)存管理 概述 什么是內(nèi)存管理 應(yīng)用程序內(nèi)存管理是在程序運(yùn)行時(shí)分配內(nèi)存(比如創(chuàng)建一個(gè)對象,會增加內(nèi)存占用)與...
    蚊香醬閱讀 5,824評論 8 119
  • 內(nèi)存管理是程序在運(yùn)行時(shí)分配內(nèi)存、使用內(nèi)存,并在程序完成時(shí)釋放內(nèi)存的過程。在Objective-C中,也被看作是在眾...
    蹲瓜閱讀 3,365評論 1 8
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,081評論 1 16
  • 為什么進(jìn)行內(nèi)存管理? 由于移動(dòng)設(shè)備的內(nèi)存極其有限,所以每個(gè)APP所占的內(nèi)存也是有限制的,當(dāng)app所占用的內(nèi)存較多時(shí)...
    天天想念閱讀 989評論 1 6
  • 很多年前的文字,今天整理了一下。照片很渣。原片找不到,這些是翻拍的。 N年前去了趟羅馬,馬不停蹄轉(zhuǎn)了6天,當(dāng)時(shí)覺得...
    joanren閱讀 2,211評論 11 38

友情鏈接更多精彩內(nèi)容