在Objective-C中采用的是引用計數的內存回收方式。凡是繼承NSObject的類生成的對象,當對象的計數為0,會對對象執行dealloc並回收。
二、alloc, retain, release
1、alloc:用來分配內存,在利用alloc生成分配了一個對象內存后,該對象的引用計數是1。
如:
ClassA *obj = [[ClassA alloc] init];
//
retainCount 為1
2、release:用來將對象的引用計數減一, 當我們不在需要這個對象調用obj的release方法使引用計數變為0, obj的dealloc方法會被自動調用。
接上例:
[obj release];
//
retainCount 減一變為0,obj的dealloc會被調用
3、 retain:用來將對象的引用計數加一。
如:
ClassA *obj = [[ClassA alloc] init];
//
retainCount 為1
[obj retain]; // retainCount為2
// do something
[obj release]; // retainCount為1
[obj release]; // retainCount為0,調用dealloc 對象釋放
[obj retain]; // retainCount為2
// do something
[obj release]; // retainCount為1
[obj release]; // retainCount為0,調用dealloc 對象釋放
在這個環節上容易出的錯誤:
ClassA *obj1 = [[ClassA alloc] init];
//
ClassA里面有個 名為hello的function
ClassA *obj2 = obj1;
// do something with obj1 and obj2
[obj1 hello];
[obj1 release]; // 對象已經釋放掉了
[obj2 hello]; // 崩潰,obj2已經是野指針了
[obj2 release];
ClassA *obj2 = obj1;
// do something with obj1 and obj2
[obj1 hello];
[obj1 release]; // 對象已經釋放掉了
[obj2 hello]; // 崩潰,obj2已經是野指針了
[obj2 release];
三、autorelease, NSAutoreleasePool 和工廠方法
1 、NSAutoreleasePool:在開發的過程中會出現內存申請者經常會出現內存申請者和內存使用者之間的交互問題,比如,一個函數內部申請了內存,做了一些操作,然后返回這塊內存。這個時候調用者要負責對這塊內存執行清理工作,在調用棧很深的情況下,很容易產生內存泄露。而且這種內存解決方案破壞了內存由誰生成由誰釋放的原則。
為了解決這個問題,產生了NSAutoreleasePool。使用方法如下:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//
創建一個release pool
ClassA *obj = [[[ClassA alloc] init] autorelease]; // 創建一個對象並將它加入release pool
[obj retainCount]; // retainCount為1
// do something
[pool release]; // 在release pool被釋放的時候將obj的引用計數自動減一,這時
// obj的releaseCount變為了0,obj被釋放
ClassA *obj = [[[ClassA alloc] init] autorelease]; // 創建一個對象並將它加入release pool
[obj retainCount]; // retainCount為1
// do something
[pool release]; // 在release pool被釋放的時候將obj的引用計數自動減一,這時
// obj的releaseCount變為了0,obj被釋放
在對象被
2、工廠方法:在UIKit提供的類中經常會看到static方法創建出的對象,如:
NSNumber *numObj = [NSNumber numberWithInt:
1];
從設計模式的角度上講這個static方法創建了,一個對象,這中利用公共方法創建對象的模式叫做工廠方法。
在Objective-C中的程序設計中有個規則,就是這種工廠方法返回的對象都是不需要調用者release的,因為雖然他們現在的引用計數為1,但是他會自動釋放。(因為他們在返回對象的時候都對其調用了autorelease)
3、深入講解NSAutoreleasePool
NSAutoreleasePool是蘋果公司實現的類,我們無法知道他是怎么實現的,但是可以確定的是,在對象調用autorelease的時候,距離對象最近的NSAutoreleasePool將記錄該對象,並在autorelease pool釋放時將對象的計數減一。
NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
//
創建第一個release pool
ClassA *obj1 = [[ClassA alloc] init] autorelease]; // 生成一個對象,調用autorelease,對象
// 被加載到pool1里,obj1的引用計數為1
[obj1 retain]; // obj1引用計數加1,變為2
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; // 創建第二個release pool
ClassA *obj2 = [[ClassA alloc] init] autorelease]; // 生成另一個,調用autorelease,對象
// 被加載到pool2里,obj2的引用計數為1
[obj1 autorelease]; // obj1調用autorelease,被加載到pool2里,引用計數為2
[pool2 release]; // 釋放pool2,obj1的引用計數減1,變為1
// obj2的引用計數減1,變為0,obj2對象釋放
[poo1 release]; // 釋放pool1,obj1的引用計數減1,變為0,obj1對象釋放
ClassA *obj1 = [[ClassA alloc] init] autorelease]; // 生成一個對象,調用autorelease,對象
// 被加載到pool1里,obj1的引用計數為1
[obj1 retain]; // obj1引用計數加1,變為2
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; // 創建第二個release pool
ClassA *obj2 = [[ClassA alloc] init] autorelease]; // 生成另一個,調用autorelease,對象
// 被加載到pool2里,obj2的引用計數為1
[obj1 autorelease]; // obj1調用autorelease,被加載到pool2里,引用計數為2
[pool2 release]; // 釋放pool2,obj1的引用計數減1,變為1
// obj2的引用計數減1,變為0,obj2對象釋放
[poo1 release]; // 釋放pool1,obj1的引用計數減1,變為0,obj1對象釋放
NSAutoreleasePool是需要在release自身的時候對掛在它上面的對象(調用過autorelease的對象)進行引用計數減一的,但是,我們能看見的顯式創建的NSAutoreleasePool只在main函數中有創建,難道程序在運行過程中調用autorelease的對象必須在程序退出時釋放么?
絕對不可能,如果是在main函數結束時釋放,在程序運行中必然造成內存使用量一直上升。 實際上,在主線程運行中,系統會自動創建一個NSAutoreleasePool並在一次繪圖完成以后釋放這個pool。(忘記看了那份文檔說的了,自己實驗的結果是如此,等我找到了文檔再分享給大家)
四、聲明一個property,利用assign, retain或者copy
利用property可以快速創建一個屬性
利用property創建一個屬性之后,根據設置(assign, retain或者copy)系統生成的Setter和Getter是不同的。(以NSString為例)
在調用self.name進行賦值時會調用setter(setName:)在進行取值時會調用getter(getName)
公用部分:
//
.h文件
@property (nonatomic, XXXX) NSString *name;
// .m文件
@synthesize name = _name;
@property (nonatomic, XXXX) NSString *name;
// .m文件
@synthesize name = _name;
1. 如果XXXX是assign,系統會自動生成setter和getter如下:
setter:
- ( void) setName: (NSString *) newName {
_name = newName;
}
getter:
- (NSString *) getName {
return _name;
}
- ( void) setName: (NSString *) newName {
_name = newName;
}
getter:
- (NSString *) getName {
return _name;
}
2. 如果XXXX是retain,系統會自動生成setter和getter如下:
setter:
- ( void)setName: (NSString *)newName {
if (_name == newName) {
return;
}
[_name release]; // 舊的對象引用計數減一
_name = [newName retain]; // 將新的對象引用計數加一
}
getter:
- (NSString *) getName {
return _name;
}
- ( void)setName: (NSString *)newName {
if (_name == newName) {
return;
}
[_name release]; // 舊的對象引用計數減一
_name = [newName retain]; // 將新的對象引用計數加一
}
getter:
- (NSString *) getName {
return _name;
}
3.如果XXXX是copy,系統會自動生成setter和getter如下:
setter:
- ( void)setName: (NSString *)newName {
if (_name == newName) {
return;
}
[_name release]; // 舊的對象引用計數減一
_name = [newName copy]; // 將新的對象引用計數加一 ,
}
getter:
- (NSString *) getName {
return _name;
}
- ( void)setName: (NSString *)newName {
if (_name == newName) {
return;
}
[_name release]; // 舊的對象引用計數減一
_name = [newName copy]; // 將新的對象引用計數加一 ,
}
getter:
- (NSString *) getName {
return _name;
}
_name是一個屬性,如果對其直接做賦值操作,是不會對對象的引用計數產生影響的,因此盡量不要直接對其進行賦值操作,可以避免很多錯誤。
如果是用retain和copy聲明的屬性,有一些場景需要直接對_name執行釋放(如需要立即釋放內存的dealloc,關掉網絡請求等),這時要直接調用[_name release]將內存釋放,並且將_name變量設置為nil。
if (_name != nil) {
[_name release], _name = nil;
}
// 或者
self.name = nil;
// 錯誤的方式
if(_name != nil) {
[_name release]; // _name引用計數為0,_name 所指被釋放
}
self.name = nil; // 這里肯定崩潰, 因為在前面,_name已經被release了。
// 但由於_name沒有賦值為nil,變成了野指針。
// 前面說過,self.name執行的setter會先將
// 舊的_name執行release,這時造成崩潰。
[_name release], _name = nil;
}
// 或者
self.name = nil;
// 錯誤的方式
if(_name != nil) {
[_name release]; // _name引用計數為0,_name 所指被釋放
}
self.name = nil; // 這里肯定崩潰, 因為在前面,_name已經被release了。
// 但由於_name沒有賦值為nil,變成了野指針。
// 前面說過,self.name執行的setter會先將
// 舊的_name執行release,這時造成崩潰。
五、其他注意事項
1. 自己創建了thread后,第一件事就是創建NSAutoreleasePool。(來自蘋果線程安全手冊)
2. NSArray,NSDictionary,NSSet等容器類會自動將引用添加到其中的對象引用計數加一,當移除對象或者釋放容器對象自身的時候引用計數減一。