因此,咱們須要對內存進行合理的分配內存、清除內存,回收那些不須要再使用的對象。從而保證程序的穩定性。java
那麼,那些對象才須要咱們進行內存管理呢?程序員
這是由於數據結構
堆
裏邊。堆
:通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式相似於鏈表棧
裏面棧
:由操做系統自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧(先進後出)int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10; // 棧
int b = 20; // 棧
// p : 棧
// Person對象(計數器==1) : 堆
Person *p = [[Person alloc] init];
}
// 通過上面代碼後, 棧裏面的變量a、b、p 都會被回收
// 可是堆裏面的Person對象還會留在內存中,由於它是計數器依然是1
return 0;
}
複製代碼
提供給Objective-C程序員的基本內存管理模型有如下3種:函數
系統是根據對象的引用計數器來判斷何時須要回收一個對象所佔用的內存ui
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要建立一個對象默認引用計數器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1
// 只要給對象發送一個retain消息, 對象的引用計數器就會+1
[p retain];
NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 經過指針變量p,給p指向的對象發送一條release消息
// 只要對象接收到release消息, 引用計數器就會-1
// 只要一個對象的引用計數器爲0, 系統就會釋放對象
[p release];
// 須要注意的是: release並不表明銷燬\回收對象, 僅僅是計數器-1
NSLog(@"retainCount = %lu", [p retainCount]); // 1
[p release]; // 0
NSLog(@"--------");
}
// [p setAge:20]; // 此時對象已經被釋放
return 0;
}
複製代碼
- (void)dealloc
{
NSLog(@"Person dealloc");
// 注意:super dealloc必定要寫到全部代碼的最後
// 必定要寫在dealloc方法的最後面
[super dealloc];
}
複製代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 執行完引用計數爲1
[p release]; // 執行完引用計數爲0,實例對象被釋放
[p release]; // 此時,p就變成了野指針,再給野指針p發送消息就會報錯
[p release];
}
return 0;
}
複製代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 執行完引用計數爲1
[p release]; // 執行完引用計數爲0,實例對象被釋放
p = nil; // 此時,p變爲了空指針
[p release]; // 再給空指針p發送消息就不會報錯了
[p release];
}
return 0;
}
複製代碼
由於多個對象之間每每是聯繫的,因此管理起來比較複雜。這裏用一個玩遊戲例子來類比一下。spa
遊戲能夠提供給玩家(A類對象) 遊戲房間(B類對象)來玩遊戲。操作系統
下面來定義兩個類 玩家類:Person 和 房間類:Room3d
房間類:Room,房間類中有房間號指針
#import <Foundation/Foundation.h>
@interface Room : NSObject
@property int no; // 房間號
@end
複製代碼
玩家類:Personcode
#import <Foundation/Foundation.h>
#import "Room.h"
@interface Person : NSObject
{
Room *_room;
}
- (void)setRoom:(Room *)room;
- (Room *)room;
@end
複製代碼
如今咱們經過幾個玩家使用房間的不一樣應用場景來逐步深刻理解內存管理。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.建立兩個對象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房間 r
r.no = 888; // 房間號賦值
[r release]; // 釋放房間
[p release]; // 釋放玩家
}
return 0;
}
複製代碼
上述代碼執行完前3行
// 1.建立兩個對象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房間 r
r.no = 888; // 房間號賦值
複製代碼
以後在內存中的表現以下圖所示:
可見,Room實例對象和Person實例對象之間沒有相互聯繫,因此各自釋放不會報錯。執行完四、5行代碼
[r release]; // 釋放房間
[p release]; // 釋放玩家
複製代碼
後,將房間對象和玩家對象各自釋放掉,在內存中的表現以下圖所示:
最後各自實例對象的內存就會被系統回收
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.建立兩個對象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房間 r
r.no = 888; // 房間號賦值
// 將房間賦值給玩家,表示玩家在使用房間
// 玩家須要使用這間房,只要玩家在,房間就必定要在
p.room = r; // [p setRoom:r]
[r release]; // 釋放房間
// 在這行代碼以前,玩家都沒有被釋放,可是由於玩家還在,那麼房間就不能銷燬
NSLog(@"-----");
[p release]; // 釋放玩家
}
return 0;
}
複製代碼
上邊代碼執行完前3行的時候和以前在內存中的表現同樣,如圖
當執行完第4行代碼p.room = r;
時,由於調用了setter方法,將Room實例對象賦值給了Person的成員變量,不作其餘設置的話,在內存中的表現以下圖(作法不對):
在調用setter方法的時候,由於Room實例對象多了一個Person對象引用,因此應將Room實例對象的引用計數+1纔對,即setter方法應該像下邊同樣,對room進行一次retain操做。
- (void)setRoom:(Room *)room // room = r
{
// 對房間的引用計數器+1
[room retain];
_room = room;
}
複製代碼
那麼執行完第4行代碼p.room = r;
,在內存中的表現爲:
繼續執行第5行代碼[r release];
,釋放房間,Room實例對象引用計數-1,在內存中的表現以下圖所示:
而後執行第6行代碼[p release];
,釋放玩家。這時候由於玩家不在房間裏了,房間也沒有用了,因此在釋放玩家的時候,要把房間也釋放掉,也就是在delloc裏邊對房間再進行一次release操做。
這樣對房間對象來講,每一次retain/alloc操做都對應一次release操做。
- (void)dealloc
{
// 人釋放了, 那麼房間也須要釋放
[_room release];
NSLog(@"%s", __func__);
[super dealloc];
}
複製代碼
那麼在內存中的表現最終以下圖所示:
最後實例對象的內存就會被系統回收
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.建立兩個對象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房間 r
r.no = 888; // 房間號賦值
// 2.將房間賦值給玩家,表示玩家在使用房間
p.room = r; // [p setRoom:r]
[r release]; // 釋放房間 r
// 3. 換房
Room *r2 = [[Room alloc] init];
r2.no = 444;
p.room = r2;
[r2 release]; // 釋放房間 r2
[p release]; // 釋放玩家 p
}
return 0;
}
複製代碼
執行下邊幾行代碼
// 1.建立兩個對象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房間 r
r.no = 888; // 房間號賦值
// 2.將房間賦值給玩家,表示玩家在使用房間
p.room = r; // [p setRoom:r]
[r release]; // 釋放房間 r
複製代碼
以後的內存表現爲:
接着執行換房操做而不進行其餘操做的話,
// 3. 換房
Room *r2 = [[Room alloc] init];
r2.no = 444;
p.room = r2;
複製代碼
內存的表現爲:
最後執行完
[r2 release]; // 釋放房間 r2
[p release]; // 釋放玩家 p
複製代碼
內存的表現爲:
能夠看出房間 r 並無被釋放,這是由於在進行換房的時候,並無對房間 r 進行釋放。因此應在調用setter方法的時候,對以前的變量進行一次release操做。具體setter方法代碼以下:
- (void)setRoom:(Room *)room // room = r
{
// 將之前的房間釋放掉 -1
[_room release];
// 對房間的引用計數器+1
[room retain];
_room = room;
}
}
複製代碼
這樣在執行完p.room = r2;
以後就會將 房間 r 釋放掉,最終內存表現爲:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.建立兩個對象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
// 2.將房間賦值給人
p.room = r; // [p setRoom:r]
[r release]; // 釋放房間 r
// 3.再次使用房間 r
p.room = r;
[r release]; // 釋放房間 r
[p release]; // 釋放玩家 p
}
return 0;
}
複製代碼
執行下面代碼
// 1.建立兩個對象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
// 2.將房間賦值給人
p.room = r; // [p setRoom:r]
[r release]; // 釋放房間 r
複製代碼
以後的內存表現爲:
而後再執行p.room = r;
,由於setter方法會將以前的Room實例對象先release掉,此時內存表現爲:
此時_room、r 已經變成了一個野指針。以後再對野指針 r 發出retain消息,程序就會崩潰。因此咱們在進行setter方法的時候,要先判斷一下是不是重複賦值,若是是同一個實例對象,就不須要重複進行release和retain。換句話說,若是咱們使用的仍是以前的房間,那換房的時候就不須要對這個房間再進行release和retain。則setter方法具體代碼以下:
- (void)setRoom:(Room *)room // room = r
{
// 只有房間不一樣才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 將之前的房間釋放掉 -1
[_room release];
// 對房間的引用計數器+1
[room retain];
_room = room;
}
}
複製代碼
由於retain不只僅會對引用計數器+1, 並且還會返回當前對象,因此上述代碼可最終簡化成:
- (void)setRoom:(Room *)room // room = r
{
// 只有房間不一樣才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 將之前的房間釋放掉 -1
[_room release];
_room = [room retain];
}
}
複製代碼
以上就是setter方法的最終形式。