目錄
- 內(nèi)存管理原理
- 自動(dòng)釋放池
- 內(nèi)存管理原則
程序運(yùn)行過程中要?jiǎng)?chuàng)建大量的對(duì)象吩坝,OC中對(duì)象是存儲(chǔ)在堆中的,系統(tǒng)不會(huì)自動(dòng)釋放堆中的內(nèi)存。如果一個(gè)對(duì)象創(chuàng)建并使用后沒有得到及時(shí)釋放着憨,那么就會(huì)占用大量的內(nèi)存。
OC中基本類型是由系統(tǒng)進(jìn)行管理务嫡,放在棧上
在引入ARC(Automatic Reference Counting)自動(dòng)引用計(jì)數(shù)機(jī)制之前甲抖,OC的內(nèi)存管理需要由開發(fā)人員手動(dòng)維護(hù)。
在Xcode4.2之后的版本中??引入了ARC心铃,程序編譯時(shí)准谚,Xcode可以自動(dòng)為你的代碼添加內(nèi)存釋放代碼,如果編寫手動(dòng)釋放代碼Xcode會(huì)報(bào)錯(cuò)去扣,因此如果使用的Xcode4.2之后的版本柱衔,必須手動(dòng)關(guān)閉ARC,這樣才有助于我們理解OC的內(nèi)存管理機(jī)制愉棱。
為了理解OC的內(nèi)存管理機(jī)制唆铐,需要在Xcode中關(guān)閉ARC:項(xiàng)目屬性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting設(shè)置為No即可。
內(nèi)存管理原理
在C#中都有GC在自動(dòng)管理內(nèi)存奔滑,但是在OC中沒有垃圾回收機(jī)制艾岂,那么OC中內(nèi)存又是如何管理呢?其實(shí)在OC中內(nèi)存的管理是依賴對(duì)象引用計(jì)數(shù)器(reference counting)來進(jìn)行的朋其。
OC中每個(gè)對(duì)象都有一個(gè)與之對(duì)應(yīng)的整數(shù)王浴,叫“引用計(jì)數(shù)器”,當(dāng)一個(gè)對(duì)象在創(chuàng)建之后它的引用計(jì)數(shù)器值加1令宿,當(dāng)調(diào)用這個(gè)對(duì)象的alloc叼耙、retain、new粒没、copy方法之后引用計(jì)數(shù)器值自動(dòng)在原來的基礎(chǔ)上加1筛婉,當(dāng)調(diào)用這個(gè)對(duì)象的release方法之后它的引用計(jì)數(shù)器值減1,如果一個(gè)對(duì)象的引用計(jì)數(shù)器值為0,則系統(tǒng)會(huì)自動(dòng)調(diào)用這個(gè)對(duì)象的dealloc方法來銷毀這個(gè)對(duì)象爽撒。
下面通過代碼看一下引用計(jì)數(shù)器是如何工作的入蛆。
WZKPerson.h
#import <Foundation/Foundation.h>
@interface WZKPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
@end
WZKPerson.m
#import "WZKPerson.h"
@implementation WZKPerson
-(void)dealloc
{
self.name=nil;
/*最后一定要調(diào)用父類的dealloc方法;
目的:一是父類可能有其他引用對(duì)象需要釋放硕勿;二是當(dāng)前對(duì)象真正的釋放操作是在super的dealloc中完成的哨毁;
*/
[super dealloc];
}
@end
main.m(部分代碼)
//調(diào)用alloc,引用計(jì)數(shù)+1
WZKPerson *personTest=[[WZKPerson alloc] init];
personTest.name=@"test";
personTest.age=30;
//輸出personTest對(duì)象的引用計(jì)數(shù)
NSLog(@"personTest的引用計(jì)數(shù):%lu",[personTest retainCount]);
//輸出結(jié)果:personTest的引用計(jì)數(shù):1
//執(zhí)行personTest的dealloc方法
//調(diào)用過release方法之后源武,personTest指向的對(duì)象就會(huì)被銷毀扼褪,但是此時(shí)變量personTest中還存放著WZKPerson對(duì)象的地址
[personTest release];
//如果不設(shè)置personTest=nil,則personTest就是一個(gè)野指針粱栖,它指向的內(nèi)存不屬于這個(gè)程序话浇,非常危險(xiǎn)
personTest=nil;
//如果不設(shè)置personTest=nil,此時(shí)再調(diào)用personTest的release方法會(huì)報(bào)錯(cuò)
//如果設(shè)置了personTest=nil闹究,此時(shí)personTest已經(jīng)是空指針了幔崖,則oc中給空指針發(fā)送消息是不會(huì)報(bào)錯(cuò)的
[personTest release];
WZKPerson *personTest2=[[WZKPerson alloc] init];
personTest2.name=@"test2";
personTest2.age=30;
//輸出結(jié)果:personTest的引用計(jì)數(shù):1
NSLog(@"personTest2的引用計(jì)數(shù):%lu",[personTest2 retainCount]);
//引用計(jì)數(shù)+1
[personTest2 retain];
//輸出結(jié)果:personTest的引用計(jì)數(shù):2
NSLog(@"personTest2的引用計(jì)數(shù):%lu",[personTest2 retainCount]);
//引用計(jì)數(shù)-1
[personTest2 release];
//輸出結(jié)果:personTest的引用計(jì)數(shù):1
NSLog(@"personTest2的引用計(jì)數(shù):%lu",[personTest2 retainCount]);
//執(zhí)行personTest2的dealloc方法
[personTest2 release];
personTest2=nil;
在上述代碼中,可以通過dealloc方法來查看是否一個(gè)對(duì)象已經(jīng)被回收渣淤,如果沒有回收赏寇,則有可能造成內(nèi)存泄漏。
如果一個(gè)對(duì)象被釋放后价认,那么最后引用它的變量需要手動(dòng)設(shè)置為nil嗅定,否則可能造成野指針錯(cuò)誤。
注意:OC中給空對(duì)象發(fā)送消息是不會(huì)引起錯(cuò)誤
自動(dòng)釋放池
在OC中存在著一種內(nèi)存自動(dòng)釋放機(jī)制叫做自動(dòng)釋放池(或自動(dòng)引用計(jì)數(shù))刻伊,但是與C#不同的是露戒,這僅僅是一種半自動(dòng)的機(jī)制,有些操作還是需要進(jìn)行手動(dòng)設(shè)置捶箱。
自動(dòng)內(nèi)存釋放使用@autoreleasepool關(guān)鍵字聲明一個(gè)代碼塊智什,如果一個(gè)對(duì)象在初始化時(shí)調(diào)用了autorelease方法,那么當(dāng)代碼塊執(zhí)行完之后丁屎,在塊中調(diào)用過autorelease方法的對(duì)象都會(huì)自動(dòng)調(diào)用一次release方法荠锭。
下面通過代碼來了解一下自動(dòng)釋放池。
WZKPerson.h
//構(gòu)造函數(shù)
-(WZKPerson *)initWithName:(NSString *)name age:(NSInteger)age;
//獲取對(duì)象的類方法
+(WZKPerson *)personWithName:(NSString *)name;
WZKPerson.m
-(WZKPerson *)initWithName:(NSString *)name age:(NSInteger)age
{
self=[super init];
if (self) {
_name=[name copy];
_age=age;
}
return self;
}
+(WZKPerson *)personWithName:(NSString *)name
{
//這里調(diào)用了autorelease
//OC類庫(kù)中的類方法一般都不需要手動(dòng)釋放晨川,內(nèi)部已經(jīng)調(diào)用了autorelease方法证九;
WZKPerson *person=[[[WZKPerson alloc] init] autorelease];
return person;
}
main.m(部分代碼)
int main(int argc, const char * argv[]) {
@autoreleasepool {
WZKPerson *person1=[[WZKPerson alloc] init];
//調(diào)用autorelease方法,后面就不需要手動(dòng)調(diào)用release方法了
[person1 autorelease];
//由于autorelease是延遲釋放(延遲到自動(dòng)釋放池銷毀)共虑,
//所以這里仍然可以使用person1對(duì)象
person1.name=@"Kevin";
//調(diào)用autorelease方法
WZKPerson *person2=[[[WZKPerson alloc] initWithName:@"Kevin" age:27] autorelease];
//內(nèi)部已經(jīng)調(diào)用了autorelease愧怜,所以不需要手動(dòng)釋放
//另外由于內(nèi)存管理原則慌核,在外部不使用alloc蕊爵、new、copy操作读处,
//就不需要調(diào)用release或autorelease,所以這個(gè)操作是放到類方法內(nèi)部進(jìn)行完成
WZKPerson *person3=[WZKPerson personWithName:@"Kevin"];
}
return 0;
}
下面我們對(duì)自動(dòng)內(nèi)存釋放稍作總結(jié):
- autorelease方法不會(huì)改變對(duì)象的引用計(jì)數(shù)器猜惋,只是將這個(gè)對(duì)象放到自動(dòng)釋放池中丸氛;
- 自動(dòng)釋放池實(shí)質(zhì)是當(dāng)自動(dòng)釋放池銷毀之后,調(diào)用release方法著摔,但是不一定能夠銷毀對(duì)象缓窜,例如:當(dāng)對(duì)象引用計(jì)數(shù)器值大于1時(shí),該對(duì)象就無法銷毀谍咆;
- 由于自動(dòng)釋放池最后統(tǒng)一銷毀對(duì)象禾锤,因此如果一個(gè)操作比較占用內(nèi)存,例如:對(duì)象較多或者對(duì)象占用資源較多摹察,最好不要放到自動(dòng)釋放池或者放到多個(gè)自動(dòng)釋放池时肿;
- OC中類庫(kù)的類方法一般都不需要手動(dòng)釋放,因?yàn)閮?nèi)部已經(jīng)調(diào)用了autorelease方法港粱;
內(nèi)存管理原則
關(guān)于內(nèi)存管理,總結(jié)起來可以用三條原則概括:
- 使用new旦签、alloc查坪、copy方法創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的保留計(jì)數(shù)器值為1宁炫。當(dāng)不再使用該對(duì)象時(shí)偿曙,應(yīng)該向該對(duì)象發(fā)送一條release或autorelease消息。這樣該對(duì)象在其使用壽命結(jié)束時(shí)被銷毀羔巢;
- 當(dāng)你獲得一個(gè)對(duì)象時(shí)望忆,假設(shè)該對(duì)象的保留計(jì)數(shù)器值為1,而且已經(jīng)被設(shè)置為自動(dòng)釋放竿秆,那么你不需要執(zhí)行任何操作來確保該對(duì)象得到清理启摄。如果你打算在一段時(shí)間內(nèi)擁有該對(duì)象,則需要保留它并確保在操作完成時(shí)釋放它幽钢。
- 如果你保留了某個(gè)對(duì)象歉备,就需要(最終)釋放或自動(dòng)釋放該對(duì)象。必須保持retain方法和release方法的使用次數(shù)相等匪燕。
注:對(duì)象之間可能交叉引用蕾羊,此時(shí)需要遵循一個(gè)法則:誰創(chuàng)建,誰釋放