iOS內(nèi)存管理
概述
什么是內(nèi)存管理
應(yīng)用程序內(nèi)存管理是在程序運行時分配內(nèi)存(比如創(chuàng)建一個對象,會增加內(nèi)存占用)與清除內(nèi)存(比如銷毀一個對象,會減少內(nèi)存占用)的過程
為什么要管理內(nèi)存
目前iPhone手機內(nèi)存大多為1G,分配給每個應(yīng)用程序的內(nèi)存空間極其有限,當(dāng)應(yīng)用程序運行過程中所占用的內(nèi)存較大時,便會收到系統(tǒng)給出的內(nèi)存警告,如果應(yīng)用程序所占用的內(nèi)存超過限制時,便會被系統(tǒng)強制關(guān)閉,所以我們需要對應(yīng)用程序進行內(nèi)存管理,一個好的程序程序也應(yīng)該盡可能少地占用內(nèi)存
內(nèi)存管理的方式
在OC中提供了兩種管理內(nèi)存的方式
- MRR(Manual Retain-Release),也被人稱作MRC(Manual Reference Counting,手動引用計數(shù))
- ARC(Automatic Reference Counting,自動引用計數(shù))
注: 在Build Setting中的Objective-C Automatic Reference Counting設(shè)置為YES即為ARC
內(nèi)存管理的范圍
任何繼承自NSObject的對象都需要管理內(nèi)存(因存放在堆里面),基本數(shù)據(jù)類型int、float闰围、double、char、結(jié)構(gòu)體struct以及枚舉enum都不需要管理內(nèi)存(因存放在棧里面)
- 堆: 一般由程序員分配釋放內(nèi)存,若程序員不釋放,程序結(jié)束時可能由OS釋放,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的鏈表
- 棧: 由操作系統(tǒng)自動分配釋放,存放函數(shù)的參數(shù)值,局部變量值等,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧(先進后出)
內(nèi)存管理的規(guī)則
內(nèi)存管理模型基于對象所有權(quán)機制,一個對象的所有權(quán)可以擁有一個或者多個擁有者,只要一個對象的所有權(quán)擁有至少一個擁有者,該對象就會一直存在于內(nèi)存中.如果一個對象的所有權(quán)沒有任何擁有者,那么系統(tǒng)便會自動地將該對象所占用的內(nèi)存釋放掉.為了明確何時擁有某個對象的所有權(quán)幌衣、何時放棄某個對象的所有權(quán),系統(tǒng)設(shè)置了如下規(guī)則
- 你擁有你創(chuàng)建的對象的所有權(quán): 你擁有使用alloc,new,copy或者mutableCopy方法創(chuàng)建的新對象的所有權(quán)
- 你可以通過retain來獲取一個對象的所有權(quán): 在兩種情形中會使用到retain來獲取一個對象的所有權(quán)
- 在init方法和setter方法中,使用retain來獲取一個想要保存為屬性值的對象的所有權(quán)
- 防止一個對象在一些操作中被設(shè)置為無效,具體參見"內(nèi)存管理中需要注意的兩個使用場景"
- 你必須放棄你不再需要擁有其所有權(quán)的對象的所有權(quán): 你必須通過release或者autorelease方法來放棄你不再需要擁有其所有權(quán)的對象的所有權(quán),在Cocoa術(shù)語中"放棄一個對象的所有權(quán)"一般被稱作"釋放一個對象"
- 你不能放棄一個你不擁有的對象的所有權(quán): 這個規(guī)則是以上幾點規(guī)則的推論
iOS內(nèi)存管理之MRR&MRC
概述
在iOS5以前,程序員普遍使用MRR&MRC方式來管理內(nèi)存,程序員需要自己添加retain、release和autorelease等內(nèi)存管理代碼來跟蹤自己所擁有的對象以明確地管理對象的內(nèi)存,在需要使用該對象的時候保證該對象一直存在于內(nèi)存中,在不需要使用該對象的時候保證該對象所占用的內(nèi)存被系統(tǒng)正吃不耍回收
為了讓系統(tǒng)知道何時需要將某個對象所占用的內(nèi)存清理掉,系統(tǒng)引入了引用計數(shù)器的概念
引用計數(shù)器
概述
系統(tǒng)為每個OC對象內(nèi)部都分配了4個字節(jié)的存儲空間存放自己的引用計數(shù)器,引用計數(shù)器是一個整數(shù),表示“對象被引用的次數(shù)”,當(dāng)對象的引用計數(shù)器為0時,對象所占用的內(nèi)存空間就會被系統(tǒng)自動回收,當(dāng)對象的引用計數(shù)器不為0時,在程序運行過程中所占用的內(nèi)存會一直存在,直到整個程序退出時由OS自動釋放
操作引用計數(shù)器
- 當(dāng)使用alloc远舅、new、copy僵控、mutableCopy創(chuàng)建一個新對象時,該新對象的引用計數(shù)器為1
- 當(dāng)給對象發(fā)送一條retain消息時,對象的引用計數(shù)器+1(方法返回對象本身)
- 當(dāng)給對象發(fā)送一條release消息時對象的引用計數(shù)器-1(方法無返回值)
- 當(dāng)給對象發(fā)送一條retainCount消息時,返回對象的當(dāng)前引用計數(shù)器(不要以該數(shù)據(jù)來判斷對象是否被釋放)
注: 給對象發(fā)送一條release消息,不代表對象會被釋放,只有對象的引用計數(shù)器為0時才會被釋放
示例
下面通過一個小示例,來演示一下如何操作對象的引用計數(shù)器
Person *p = [[Person alloc] init]; // 使用alloc創(chuàng)建一個新對象,對象引用計數(shù)器 = 1
[p retain]; // 給對象發(fā)送一條retain消息,對象引用計數(shù)器 + 1 = 2
[p release]; // 給對象發(fā)送一條release消息,對象引用計數(shù)器 - 1 = 1
[p release]; // 給對象發(fā)送一條release消息,對象引用計數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
僵尸對象香到、野指針與空指針
概述
下面介紹幾個關(guān)于內(nèi)存管理方面常常提到的術(shù)語
- 僵尸對象: 所占用的內(nèi)存已經(jīng)被回收的對象,僵尸對象不能再使用
- 野指針: 指向僵尸對象的指針,給野指針發(fā)送消息會報錯EXC_BAD_ACCESS錯誤:訪問了一塊已經(jīng)被回收的內(nèi)存
- 空指針: 沒有指向任何對象的指針(存儲的東西是nil,NULL,0),給空指針發(fā)送消息不會報錯,系統(tǒng)什么也不會做,所以在對象被釋放時將指針設(shè)置為nil可以避免野指針錯誤
注: 默認情況下,Xcode是不會監(jiān)聽僵尸對象的,所以需要我們自己手動開啟,開啟監(jiān)聽僵尸對象步驟為: Edit Scheme ->; Run ->; Diagnostics ->; Objective-C的Enable Zombie Objects打鉤,這樣便可以在因為僵尸對象報錯的時候給出更多錯誤信息
示例
Person *p = [[Person alloc] init]; // 引用計數(shù)器 = 1
[p release]; // 引用計數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
[p release]; // 這句給野指針發(fā)送消息,會報野指針錯誤,開啟監(jiān)聽僵尸對象會給出錯誤信息**- -[Person release]: message sent to deallocated instance 0x100206fd0
注: 如果在第一次給對象發(fā)送release消息后,立刻將指針置空,便不會出現(xiàn)野指針錯誤,因為給空指針發(fā)送消息不會報錯,系統(tǒng)什么也不會做,所以在對象被釋放時將指針設(shè)置為nil可以避免野指針錯誤
單對象內(nèi)存管理
概述
單對象的內(nèi)存管理比較簡單,當(dāng)使用alloc、new报破、copy悠就、mutableCopy創(chuàng)建一個新對象時,該新對象的引用計數(shù)器為1,我們只需要在對象不再使用的時候給其發(fā)送一次release消息,讓對象的引用計數(shù)器-1變?yōu)?即可正常釋放
示例
下面通過一個小示例,來演示一下如何對單對象進行內(nèi)存管理
// 在堆中開辟一塊內(nèi)存存放Person對象,在棧中開辟一塊內(nèi)存存儲局部變量p,p中存放的是Person對象的地址
// 當(dāng)出了大括號,局部變量p出了作用域便會被系統(tǒng)自動回收,而Person對象引用計數(shù)器為1仍然存在,這樣便造成內(nèi)存的泄漏,所以我們需要在局部變量p被系統(tǒng)回收之前,給p所指向的Person對象發(fā)送一條release消息讓對象引用計數(shù)器-1變?yōu)?
Person *p = [[Person alloc] init];
[p release];
多對象內(nèi)存管理
概述
相對于單對象內(nèi)存管理而言,多對象內(nèi)存管理顯得比較復(fù)雜,但是只要我們遵從基本的內(nèi)存管理規(guī)則,即可避免大部分內(nèi)存管理相關(guān)的問題
- 當(dāng)A對象想要擁有B對象的所有權(quán)時,一定要對B對象進行一次retain操作,這樣才能保證A對象存在期間B對象一直存在
- 當(dāng)A對象想要放棄對B對象的所有權(quán)時,一定要對B對象進行一次release操作,這樣才能保證A對象釋放了,B對象也能正常釋放
存取器方法
在涉及到多對象內(nèi)存管理時,平時我們使用的存取器方法便需要針對內(nèi)存管理進行一些調(diào)整
- getter方法規(guī)范: 直接返回實例變量
// 針對基本數(shù)據(jù)類型
- (int)age
{
return _age;
}
// 針對OC對象
- (Room *)room
{
return _room;
}
- setter方法規(guī)范:
// 針對基本數(shù)據(jù)類型,直接將新值賦值給實例變量
- (void)setAge:(int)age
{
_age = age;
}
// 針對OC對象,方式一: 判斷兩次傳入的對象是否相同,如果不同便release舊對象retain新對象,并將新對象賦值給實例變量
- (void)setRoom:(Room *)room
{
if (_room != room)
{
[_room release];
_room = [room retain];
}
}
// 針對OC對象,方式二: retain新對象,release舊對象,并將新對象賦值給實例變量
- (void)setRoom:(Room *)room
{
[room retain];
[_room release];
_room = room;
}
注1: release舊對象retain新對象原因: 避免設(shè)置兩次不同的room,在Person對象釋放時,第一次設(shè)置的對象無法被釋放,造成內(nèi)存泄露
注2: 判斷當(dāng)前對象與傳入對象是否相同原因: 避免設(shè)置兩次相同的room,在設(shè)置第二次時,將room已經(jīng)釋放了,再調(diào)用retain造成野指針錯誤
dealloc方法
當(dāng)系統(tǒng)回收對象的內(nèi)存時,系統(tǒng)會自動給該對象發(fā)送一條dealloc消息,我們一般會重寫dealloc方法,在這里給當(dāng)前對象所擁有的資源(包括實例變量)發(fā)送一條release消息(基本數(shù)據(jù)類型不用),保證自身所擁有的資源也可以正常釋放(因為在使用該資源的時候,采用retain獲取了該資源的所有權(quán),在自身釋放的同時,也應(yīng)該放棄對該資源的所有權(quán))
- (void)dealloc
{
NSLog(@"Person dealloc");
// release對象所擁有資源
[_room release];
// 設(shè)置為nil可以避免野指針錯誤(其實可以不設(shè)置,只是寫了顯得有逼格)
_room = nil;
[super dealloc];
}
注1: 不要直接調(diào)用對象的dealloc方法
注2: 重寫dealloc方法時,一定要調(diào)用[super dealloc]方法,且放在代碼的最后
注3: 當(dāng)應(yīng)用程序被關(guān)掉,dealloc方法不一定會被調(diào)用,因為由系統(tǒng)OS直接來釋放內(nèi)存比調(diào)用dealloc釋放內(nèi)存效率得多
注4: 不要在dealloc方法中管理稀缺資源(比如網(wǎng)絡(luò)連接,文件資源,DOS命令等),因為dealloc并不一定都是立即調(diào)用,有可能會延遲調(diào)用,也可能根本不會被調(diào)用
屬性@property
概述
屬性可以幫助我們免除重復(fù)地寫存取器代碼的麻煩,并且合理使用屬性修飾符也可以幫助我們生成符合內(nèi)存管理規(guī)范的代碼
兩個編譯器指令: @property和@synthesize
在Xcode4.4之前
- @property用于替代setter和getter方法的聲明
@property double weight;
- (void)setWeight:(double)weight;
- (double)weight;
- @synthesize用于替代setter和getter方法的實現(xiàn)
@synthesize weight = _weight;
- (void)setWeight:(double)weight
{
_weight = weight;
}
- (double)weight
{
return _weight;
}
注1: 在@synthesize后面告訴編譯器,將要實現(xiàn)哪個@property的實現(xiàn),在等號后邊告訴編譯器,將setter傳進來的值賦值給誰和getter返回誰的值給調(diào)用者
注2: 在@synthesize中如果不寫等號后面的,系統(tǒng)會默認將setter傳進來的值賦值給與@synthesize后面的變量同名的成員變量,且getter返回與@synthesize后面的變量同名的成員變量的值給調(diào)用者,即weight而不是_weight
在Xcode4.4之后
- @property可以直接替代setter和getter方法的聲明和實現(xiàn),同時會生成以下劃線開頭的"私有"成員變量(即在.m文件@implementation下面{}中生成的,在其他類中不能直接訪問)
注: 不用寫@synthesize了,系統(tǒng)會默認為將setter傳進來的值賦值以下劃線開頭的成員變量和getter返回以下劃線開頭的成員變量給調(diào)用者
- @property只會生成最簡單的setter和getter方法的聲明和實現(xiàn),并不會對傳入的數(shù)據(jù)進行過濾,如果想對傳入的數(shù)據(jù)進行過濾,可以自己重寫setter和getter方法的實現(xiàn)
- 如果重寫了setter方法的實現(xiàn),那么@property只會幫忙生成getter方法的實現(xiàn)
- 如果重寫了getter方法的實現(xiàn),那么@property只會幫忙生成setter方法的實現(xiàn)
- 如果同時重寫了setter方法和getter方法的實現(xiàn),那么@property就不會幫忙生成以下劃線開頭的"私有"成員變量,需要自己生成
注: 使用@property并不會幫忙實現(xiàn)dealloc中的[_name release];所以仍需要自己釋放對象所擁有的資源
屬性修飾符
屬性修飾符用來修飾@property,給@property添加特定功能,以NSNumber對象為例
- 如果不寫修飾符(即默認assign),自動生成的setter是直接賦值
@property NSNumber *number;
- (void)setNumber:(NSNumber *)number
{
_number = number;
}
- 如果寫了retain修飾符,自動生成的setter便是符合內(nèi)存管理規(guī)則的存取器方法(判斷兩次傳入的對象是否相同,如果不同便release舊對象retain新對象,并將新對象賦值給實例變量)
@property (retain) NSNumber *number;
- (void)setNumber:(NSNumber *)number
{
if (_number != number)
{
[_number release];
_number = [number retain];
}
}
在OC中有如下幾類屬性修飾符
-
retain, assign, copy
- retain自動生成的setter是判斷當(dāng)前對象與傳入對象是否相同,如果不同release舊對象retain新對象(適用于OC對象)
- assign自動生成的setter是直接賦值(默認,適用于基本數(shù)據(jù)類型)
- copy:自動生成的setter是判斷當(dāng)前對象與傳入對象是否相同,如果不同release舊對象copy新對象(適用于NSString *,詳見"iOS內(nèi)存管理之Copy")
-
readonly, readwrite
- readonly只生成getter的聲明和實現(xiàn)
- readwrite同時生成setter和getter的聲明和實現(xiàn)
-
atomic, nonatomic
- atomic性能低(默認)
- nonatomic性能高(iOS開發(fā)中99.99%使用這個)
-
setter, getter
- setter給生成的setter方法取一個名字,一定要有冒號
- getter給生成的getter方法取一個名字,一般用于BOOL類型中
// 枚舉
typedef NS_ENUM(NSInteger, Sex)
{
SexMan,
SexWoman
};
// 結(jié)構(gòu)體
typedef struct
{
int year;
int month;
int day;
} Date;
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) Sex sex;
@property (nonatomic, assign) Date birthday;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain, readonly) NSString *phone;
@property (nonatomic, assign, getter = isRich) BOOL rich;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
[_name release];
[_phone release];
[super dealloc];
}
@end
使用存取器方法時應(yīng)該注意以下使用場景
- 如果在類中聲明了一個方法用于設(shè)置實例變量,需要使用存取器方法來設(shè)置
@property (nonatomic, retain) NSNumber *count;
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
// 雖然下述方法也能實現(xiàn),但是這種方式避開了存取器方法,可能會導(dǎo)致一些錯誤(比如實例變量內(nèi)存管理規(guī)則發(fā)生了變化,并且這種方法不符合KVO規(guī)則)
- (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
- 不要在init方法和dealloc方法中使用存取器方法來訪問和修改實例變量
- init
{
self = [super init];
if (self)
{
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
- initWithCount:(NSNumber *)startingCount
{
self = [super init];
if (self)
{
_count = [startingCount copy];
}
return self;
}
- (void)dealloc
{
[_count release];
[super dealloc];
}
循環(huán)import
#import概述
import是一個預(yù)編譯指令,他會將""中的文件拷貝到import所在的位置,不過一旦""中的文件發(fā)生變化,那么import就會重新拷貝一次,從而降低編譯效率
在開發(fā)中經(jīng)常會出現(xiàn)循環(huán)import的情況,表現(xiàn)形式為在A.h文件中import了B.h文件,在B.h文件中import了A.h文件,那么A.h和B.h兩個文件會相互拷貝,造成循環(huán)import報錯
@class概述
@class ClassName;僅僅是告訴編譯器,ClassName是一個類,不會做任何拷貝操作,不過由于@class僅僅是告訴編譯器ClassName是一個類,并不知道ClassName類中到底有什么方法,所以在.m文件中使用該類時還是要#import該類
使用@class避免循環(huán)import
在開發(fā)中我們可以使用@class來避免循環(huán)import的問題,即在想要使用一個類的時候,可以在.h中使用@class ClassName;來聲明一個類,真正使用這個類的時候在.m中#import "ClassName"即可
注: 在一個ClassName發(fā)生變化時,一般只有import了它的.m類才需要重新拷貝,并不會讓其他類間接受到影響(因為其他類不會引用.m文件)
循環(huán)引用
如果A對象要擁有B對象的所有權(quán),同時B對象也要擁有A對象的所有權(quán),那么就會產(chǎn)生循環(huán)引用,導(dǎo)致兩者都無法被正常釋放
@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end
@interface Car : NSObject
@property (nonatomic, retain) Person *person;
@end
針對循環(huán)引用的問題,Cocoa建立了一種規(guī)則,就是在出現(xiàn)循環(huán)引用的情形時,不要讓A對象和B對象互相擁有對方的所有權(quán),而是讓一方(parent)擁有另一方(children)的"強"引用,另一方(children)擁有這一方(parent)的"弱"引用,即將A retain B, B retain A中一方的retain改為assign即可
@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end
@interface Car : NSObject
@property (nonatomic, assign) Person *person;
@end
注: 使用assign之后,dealloc中無需再釋放該資源
在Cocoa中使用到"弱"引用的例子包括但不局限于table data sources, outline view items, notification observers, and miscellaneous targets and delegates,在處理"弱"引用的對象時一定要小心處理,因為parent并不擁有children的所有權(quán),也就是并不能保證在使用children的時候其一定存在(可能已經(jīng)被釋放),所以在parent自身釋放時一定要告知children一下自身已經(jīng)釋放,不要再繼續(xù)給自己發(fā)消息,否則會造成應(yīng)用程序的閃退(野指針錯誤).處理辦法針對通過中心/觀察者而言,便是移除觀察者;針對delegate而言,便是setDelegate:為nil,這個操作一般都是在dealloc中進行
自動釋放池
概述
自動釋放池提供了延遲放棄一個對象的所有權(quán)的機制,比如想要在一個方法中返回一個對象,如果先使用release放棄了該對象的所有權(quán),那么return返回的對象便是一個僵尸對象,如果先進行return返回,那么便無法放棄該對象的所有權(quán),導(dǎo)致了內(nèi)存泄漏
自動釋放池的創(chuàng)建
iOS5.0之前創(chuàng)建自動釋放池方法(現(xiàn)在也可使用)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];
iOS5.0之后創(chuàng)建自動釋放池方法
@autoreleasepool
{
// do something...
}
autorelease方法
autorelease是一種支持引用計數(shù)的內(nèi)存管理方式,只要在自動釋放池中給對象發(fā)送一條autorelease消息,就會將對象放到自動釋放池中,當(dāng)自動釋放池被銷毀時,會對池中的所有對象發(fā)送一條release消息
- autorelease方法會返回對象本身
- autorelease方法不會修改對象的引用計數(shù)器
- autorelease方法可以讓開發(fā)者不用實時關(guān)心什么時候發(fā)送release消息
注1: 自動釋放池被銷毀時,只是給池中所有對象發(fā)送一條release消息,不代表對象一定會被釋放
注2: 對象在自動釋放池中每收到一條autorelease消息,在自動釋放池被銷毀時,對象都會收到一次release消息
autorelease方法使用注意事項
- 一定要在自動釋放池中調(diào)用autorelease方法,才會將對象放入自動釋放池中
- 即使在自動釋放池內(nèi)創(chuàng)建對象,只要不調(diào)用了autorelease方法,就不會將對象放入自動釋放池中
- 即使在自動釋放池外創(chuàng)建對象,只要在自動釋放池中調(diào)用了autorelease方法,就會將對象放入自動釋放池中
- 一個程序中可以創(chuàng)建N個自動釋放池,且自動釋放池可以嵌套,這些自動釋放池以棧結(jié)構(gòu)存在(先進后出),當(dāng)一個對象調(diào)用autorelease方法時,會將這個對象放到棧頂?shù)淖詣俞尫懦刂?/li>
- autorelease不能精準(zhǔn)地釋放內(nèi)存(延遲釋放),因為要將池中的所有內(nèi)容都執(zhí)行完才會釋放自動釋放池,所以占用內(nèi)存比較大的東西還是使用release為宜
創(chuàng)建自己的自動釋放池
Cocoa希望代碼在自動釋放池中執(zhí)行,否則發(fā)送了autorelease消息的對象便不能收到release消息,從而導(dǎo)致內(nèi)存泄漏.UIKit庫會在自動釋放池中處理每一個事件循環(huán),所以我們并不需要顯式地創(chuàng)建自動釋放池,不過如下三種情況下,我們?nèi)匀恍枰獎?chuàng)建自己的自動釋放池
- 如果不是基于UI庫(UI framework)進行開發(fā),而是采用命令行工具(command-line tool)進行開發(fā)
- 在使用循環(huán)時,尤其是循環(huán)次數(shù)多,且非常占用內(nèi)存的情況下,可以在循環(huán)內(nèi)部創(chuàng)建自己的自動釋放池,在每次循環(huán)時都將臨時變量銷毀,以免大量臨時變量堆積,導(dǎo)致內(nèi)存短時占用過高
- 如果大量產(chǎn)生第二線程,線程一被執(zhí)行就必須創(chuàng)建自己的自動釋放池,否則程序會內(nèi)存泄漏(這里未來需要進行詳細補充)
for (int i = 0; i < 999999; i++)
{
@autoreleasepool
{
Person *p = [[[Person alloc] init] autorelease];
// do something...
}
}
類工廠方法內(nèi)存管理
在開發(fā)中,我們經(jīng)常使用Foundation框架中的類,在調(diào)用其類工廠方法創(chuàng)建一個對象時,因為并不是使用alloc,new,copy或者mutableCopy方法創(chuàng)建的,所以并不需要我們自己在給該對象發(fā)送release或者autorelease消息,這是因為類工廠方法內(nèi)部都已經(jīng)在返回對象前進行過延遲釋放
我們在自己書寫類工廠方法時,也應(yīng)該與系統(tǒng)處理方式相同,快速返回一個autorelease對象的方式具體如下
+ (instancetype)person
{
// 使用self而不是使用Person是因為這樣可以在子類調(diào)用該方法時會返回子類的對象
return [[[self alloc] init] autorelease];
}
快速返回一個帶有參數(shù)的autorelease對象的方式具體如下
+ (instancetype)personWithName:(NSString *)name
{
Person *person = [[[self alloc] init] autorelease];
person.name = name;
return person;
}
注: Foundation框架中的類,所有通過類工廠方法創(chuàng)建的對象都是autorelease的,所以不用自己進行釋放
集合中對象的內(nèi)存管理
集合在iOS中包括NSArray、NSDictionary充易、NSSet,接下來以數(shù)組為例講解一下集合中的內(nèi)存管理規(guī)則
- 將一個對象添加到一個數(shù)組中,數(shù)組會對該對象進行一次retain操作
- 當(dāng)數(shù)組釋放后,會給數(shù)組中每個對象發(fā)送一條release消息
- 當(dāng)數(shù)組移除一個對象后,會給這個對象發(fā)送一條release消息
NSMutableArray *array = [NSMutableArray array];
for (NSUInteger i = 0; i < 10; i++)
{
NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
// allocedNumber對象被加入數(shù)組,數(shù)組會對該對象進行一次retain操作
[array addObject:allocedNumber];
[allocedNumber release];
}
// 當(dāng)array對象被釋放,系統(tǒng)會給數(shù)組中的每個allocedNumber對象發(fā)送一條release消息,保證數(shù)組中的每個元素都能正常釋放
內(nèi)存管理中需要注意的兩個使用場景
- 當(dāng)一個指針?biāo)赶虻膶ο鬄閿?shù)組中的某一個元素時,如果數(shù)組將該元素移除或者數(shù)組對象自身被釋放,此時該指針指向的對象已經(jīng)被釋放了
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject被從數(shù)組中移除,會收到一條release消息,此時heisenObject所指向的對象已經(jīng)被釋放了
// 在實際開發(fā)中,我們并不希望這樣的情況發(fā)生,所以需要使用retain擁有該對象的所有權(quán)
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// do something...
[heisenObject release];
- 當(dāng)一個指針?biāo)赶虻膶ο笫峭ㄟ^另一個對象獲得的,如果另一個對象自身被釋放,此時該指針指向的對象已經(jīng)被釋放了
id parent = <#create a parent object#>;
heisenObject = [parent child];
[parent release];
// heisenObject所指向的對象是通過另一個對象(例子中的parent對象)獲得的,另一個對象是該對象的唯一擁有者,如果另一個對象被釋放了,此時heisenObject所指向的對象已經(jīng)被釋放了
// 在實際開發(fā)中,我們并不希望這樣的情況發(fā)生,所以需要使用retain擁有該對象的所有權(quán)
id parent = <#create a parent object#>;
heisenObject = [[parent child] retain];
[parent release];
// do something...
[heisenObject release];
iOS內(nèi)存管理之ARC
概述
ARC(Automatic Reference Counting,自動引用計數(shù)),是iOS4引入的一項新技術(shù)(從iOS5開始支持弱引用),其使用與MRR&MRC相同的內(nèi)存管理規(guī)則來管理內(nèi)存,不過編譯器會在編譯階段自動地在適當(dāng)?shù)奈恢貌迦雛etain梗脾、release和autorelease等內(nèi)存管理代碼來管理內(nèi)存(屬于編譯器特性,不是運行時特性),不再需要程序人員手動管理.官方強烈建議使用ARC方式來管理內(nèi)存
注: OC中的ARC和Java中的垃圾回收機制不一樣,Java中的垃圾回收是系統(tǒng)做的,而OC中的ARC是編譯器做的
展示ARC與MRC使用區(qū)別的小示例
// 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管理內(nèi)存的優(yōu)勢
消除了手動管理內(nèi)存的煩惱,不再需要手動調(diào)用retain、release和autorelease等方法來管理內(nèi)存(實際上在ARC模式下已經(jīng)不能調(diào)用出來該方法了),編譯器在編譯階段會自動地在適當(dāng)?shù)奈恢貌迦脒@些代碼,這樣可以極大概率地避免出現(xiàn)內(nèi)存問題.同時編譯器也會在一些地方執(zhí)行某些優(yōu)化,讓代碼性能更高,官方提供了一個能夠體現(xiàn)出來這種優(yōu)勢的插圖,如下
ARC與MRC的混合開發(fā)
- 如果想在ARC項目中使用MRC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fno-objc-arc
- 如果想在MRC項目中使用ARC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fobjc-arc
ARC引入的新規(guī)則
為了使ARC能夠正常工作,在ARC中引入了一些區(qū)別于當(dāng)前編譯模式的新的規(guī)則,如果你違反了這些規(guī)則,在編譯階段編譯器會給出一個警告
- 不能實現(xiàn)或者調(diào)用retain盹靴、release炸茧、autorelease和retainCount方法,甚至不能使用@selector(retain)瑞妇、@selector(release)等方式調(diào)用
- 不能調(diào)用dealloc方法
- 可以實現(xiàn)dealloc方法,用于釋放除了實例變量以外的其他資源
- 不需要在這里釋放實例變量(實際上也不能在這里給實例變量發(fā)送release消息)
- 可以在這里調(diào)用[systemClassInstance setDelegate:nil],以便處理不是用ARC編譯的systemClass(在MRC下delegate使用assign修飾,如果自身被釋放,delegate會變成野指針,所以需要在dealloc中將其置空;在ARC下delegate使用weak修飾,如果自身被釋放,delegate會自動置空)
- 不需要調(diào)用[super dealloc],編譯器會自動調(diào)用
- 不能使用NSAutoreleasePool來創(chuàng)建自動釋放池,而是需要使用@autoreleasepool來代替
- 為了與MRC之間進行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個新的getter名稱
// 錯誤
@property NSString *newTitle;
// 正確
@property (getter=theNewTitle) NSString *newTitle;
ARC引入的新特性
兩個屬性修飾符: strong和weak
在ARC中新增了兩個屬性修飾符: strong和weak,其中strong是默認修飾符,下面介紹一下這兩個屬性修飾符與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中屬性修飾符的使用,要進行如下變化
- strong用于OC對象,相當(dāng)于MRC中的retain
- weak用于OC對象,相當(dāng)于MRC中的assign
- assign用于基本數(shù)據(jù)類型,相當(dāng)于MRC中的assign
注: 其實就是將MRC中的assign分成了兩個部分,分別用于修飾OC對象與基本數(shù)據(jù)類型
四個變量修飾符
在ARC中新增了四個變量修飾符: 雙下劃線strong、雙下劃線weak梭冠、雙下劃線unsafe_unretained和雙下劃線autoreleasing,其中雙下劃線strong是默認修飾符,下面介紹一下這四個變量修飾符
- 雙下劃線strong: 強引用,只要有強指針指向該變量,該變量便會一直存在
- 雙下劃線weak: 弱引用,只要沒有強指針指向該變量,該變量便會被置空(即設(shè)置為nil)
- 雙下劃線unsafe_unretained: 不安全的弱引用,只要沒有強指針指向該變量,該變量不會被置空(即設(shè)置為nil),而會變成野指針
- 雙下劃線autoreleasing: 用于標(biāo)示自動釋放的變量
官方提醒,在為變量添加修飾符時,最正確的方式如下
// 規(guī)則
ClassName * qualifier variableName;
// 正確示例
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
// 錯誤示例(雖然錯誤,但是編譯器會默認為正確,官方說法為"forgiven")
__weak MyClass * myWeakReference;
__unsafe_unretained MyClass * myUnsafeReference;
注: 在直接使用__weak修飾變量指向一個剛創(chuàng)建的對象時,需要注意對象剛剛創(chuàng)建出來就會釋放的情況
NSString * __weak string = [[NSString alloc] initWithFormat:@"loly"];
// 因為沒有強指針指向該對象,該對象會立即被釋放
ARC中多對象內(nèi)存管理
概述
ARC中判斷對象是否應(yīng)該被釋放,不再觀察引用計數(shù)器是否為0,思想應(yīng)該轉(zhuǎn)變?yōu)槭欠裼袕娭羔樦赶蛟搶ο?只要有一個強指針指向該對象,該對象就會一直保持在內(nèi)存中不會被釋放
- 當(dāng)A對象想要擁有B對象的所有權(quán)時,直接使用一個強指針指向它即可
- 當(dāng)A對象想要放棄對B對象的所有權(quán)時,什么都不需要做,編譯器會自動處理
在涉及到多對象內(nèi)存管理時,在MRC中使用的存取器方法也需要進行一些調(diào)整
存取器方法
- getter方法規(guī)范: 直接返回實例變量
// 針對基本數(shù)據(jù)類型
- (int)age
{
return _age;
}
// 針對OC對象
- (Room *)room
{
return _room;
}
- setter方法規(guī)范:
// 針對基本數(shù)據(jù)類型,直接將新值賦值給實例變量
- (void)setAge:(int)age
{
_age = age;
}
// 針對OC對象,直接將新值賦值給實例變量
- (void)setRoom:(Room *)room
{
_room = room;
}
循環(huán)引用
因為ARC使用與MRC相同的引用計數(shù)規(guī)則,所以同樣也會出現(xiàn)循環(huán)引用問題
在MRC中的解決辦法是讓parent持有children的"強"引用,而children持有parent的"弱"引用,即將A retain B, B retain A中一方的retain改為assign即可
在ARC中的解決辦法與在MRC中原理相同,即將A strong B, B strong A中一方的strong改為weak即可
@interface Person : NSObject
@property (nonatomic, strong) Car *car;
@end
@interface Car : NSObject
@property (nonatomic, weak) Person *person;
@end
注: 當(dāng)使用Block時,會出現(xiàn)不易察覺的循環(huán)引用現(xiàn)象,關(guān)于這點我在另一篇文章中進行過介紹,這里不再贅述
ARC中使用新的方式創(chuàng)建自動釋放池
關(guān)于自動釋放池在上文已經(jīng)詳細介紹過,這里不再贅述
// MRC
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];
// ARC
@autoreleasepool
{
// do something...
}
棧變量在ARC中默認初始化為nil
在ARC中,棧變量會被初始化為nil,即使不進行賦值,程序也不會造成崩潰,示例如下
- (void)myMethod
{
NSString *name;
NSLog(@"name: %@", name);
// 打印結(jié)果為nil,不會崩潰
}
iOS內(nèi)存管理之Copy
概述
copy(復(fù)制辕狰、拷貝)是產(chǎn)生一個副本對象的過程,只要是通過拷貝產(chǎn)生的副本對象,副本對象中的內(nèi)容與源對象中的內(nèi)容就完全一致,下面介紹幾個copy相關(guān)的知識點
copy的特點
- 修改源對象的屬性和行為,不會影響副本對象
- 修改副本對象的屬性和行為,不會影響源對象
copy與mutableCopy
- 使用copy產(chǎn)生的副本對象是不可變的(如NSString,NSArray)
- 使用mutableCopy產(chǎn)生的副本對象是可變的(如NSMutableString,NSMutableArray)
通過拷貝是否會產(chǎn)生新的對象
通過拷貝是否會產(chǎn)生新對象,就要看源對象與副本對象是否滿足拷貝的特點
- 可變對象通過mutableCopy,會生成新的對象
- 可變對象通過copy,會生成新的對象
- 不可變對象通過mutableCopy,會生成新的對象
- 不可變對象通過copy,不會生成新的對象(因為源對象與副本對象都是不可變的,已經(jīng)滿足拷貝的特點)
深拷貝(內(nèi)容拷貝)與淺拷貝(指針拷貝)
- 深拷貝: 如果通過拷貝生成了新對象,就稱為深拷貝(內(nèi)容拷貝)
- 淺拷貝: 如果通過拷貝沒生成新對象,就稱為淺拷貝(指針拷貝)
注1: 通過深拷貝,源對象的引用計數(shù)器不變,副本對象的引用計數(shù)器為1,所以需要對副本對象進行一次release操作
注2: 通過淺拷貝,源對象的引用計數(shù)器+1,所以需要對源對象再進行一次release操作
copy與retain作為屬性修飾符的不同
- 通過copy作為屬性修飾符來修飾對象的屬性,是對傳入變量進行了一次copy操作,在外界變量進行修改之后,對象的屬性不會隨之發(fā)生變化
- 通過retain作為屬性修飾符來修飾對象的屬性,是對傳入變量進行了一次retain操作,在外界變量進行修改之后,對象的屬性會隨之發(fā)生了變化
注: 字符串的屬性都應(yīng)該使用copy修飾符進行修飾
示例
@interface Person : NSObject
@property (nonatomic, copy) NSString *theCopyName;
@property (nonatomic, strong) NSString *theStrongName;
@end
Person *p = [[Person alloc] init];
NSMutableString *mStr = [NSMutableString stringWithString:@"loly"];
p.theStrongName = mStr;
p.theCopyName = mStr;
[mStr appendString:@"y"];
NSLog(@"theStrongName = %@, theCopyName = %@", p.theStrongName, p.theCopyName);
// 打印結(jié)果: theStrongName = loly, theCopyName = lol
自定義類實現(xiàn)copy與mutableCopy功能
概述
一個類的對象想擁有copy與mutableCopy功能,都需要擁有一個前提
- 使用copy的前提: 對應(yīng)類需要遵守NSCopying協(xié)議,并實現(xiàn)copyWithZone:方法
- 使用mutableCopy的前提: 對應(yīng)類需要遵守NSMutableCopying協(xié)議,并實現(xiàn)mutableCopyWithZone:方法
注: OC中大部分類已經(jīng)遵守了NSCopying,NSMutableCopying協(xié)議
下面我們將通過兩個示例來介紹一下如何讓自定義類擁有copy與mutableCopy功能
實現(xiàn)Person類的copy與mutableCopy功能
@interface Person : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
// 1.創(chuàng)建一個新對象
Person *p = [[[self class] allocWithZone:zone] init];
// 2.設(shè)置當(dāng)前對象的內(nèi)容給新的對象
p.name = _name;
p.age = _age;
// 3.返回新的對象
return p;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
Person *p = [[[self class] allocWithZone:zone] init];
p.name = _name;
p.age = _age;
return p;
}
@end
實現(xiàn)Student類(繼承自Person類)的copy與mutableCopy功能
@interface Student : Person
@property (nonatomic, assign) float height;
@end
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
// 1.創(chuàng)建一個新對象
id obj = [super copyWithZone:zone];
// 2.設(shè)置當(dāng)前對象的內(nèi)容給新的對象
[obj setHeight:_height];
// 3.返回新的對象
return obj;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
id obj = [super mutableCopyWithZone:zone];
[obj setHeight:_height];
return obj;
}
@end
參考文獻
- Advanced Memory Management Programming Guide
- Transitioning to ARC Release Notes
- 互聯(lián)網(wǎng)相關(guān)技術(shù)博客及視頻