天天看點

IOS開發基礎Object-C(08)—OC記憶體管理(2)-對象之間的記憶體管理

前幾天,我們學習了記憶體管理的基本知識,了解了記憶體管理的基本原理。那麼,今天我們來學習一下對象之間的記憶體管理,看看對象之間是如何進行記憶體管理的。首先,我們建立兩個類:Student和Book類,在Student類中聲明一個Book對象

Student.h

#import <Foundation/Foundation.h>  
#import "Book.h"  

@interface Student : NSObject  
{  
    int age;  
    Book *book;  
}  
  
@property int age;  
  
- (id) initWithAge:(int)_age;  
@property Book *book;  
  
@end            

Student .m

#import "Student.h"  
  
@implementation Student  
  
@synthesize age, book;//在Xcode4.5及以後的版本中,這一句可以省略,編譯器會自動實作getter和setter方法  
    
#pragma mark 構造方法  
- (id)initWithAge:(int)_age{  
    if(self = [super init])  
        age = _age;  
      
    return self;  
}  
  
#pragma mark 回收對象  
- (void)dealloc{  
      
    NSLog(@"student %i 被銷毀了", age);  
      
    [super dealloc];  //不要忘了這一句,而且是放在最後的。

}  
  
  
@end             

我們再來進行Book類的聲明與實作

Book.h

#import <Foundation/Foundation.h>  
  
@interface Book : NSObject  
{  
    float price;  
}  
  
@property float price;  
  
- (id)initWithPrice:(float)_price;  
  
  
@end             

Book.m

#import "Book.h"  
  
@implementation Book  
  
@synthesize price;//在Xcode4.5及以後的版本中,這一句可以省略,編譯器會自動實作getter和setter方法  
  
- (id)initWithPrice:(float)_price{  
    if(self = [super init])  
        price = _price;  
      
    return self;  
}  
  
- (void)dealloc{  
    NSLog(@"book %f 被銷毀了", price);  
      
    [super dealloc];}  
  
@end             

main.m

#import <Foundation/Foundation.h>  
#import "Student.h"  
#import "Book.h"  
  
int main(int argc, const char * argv[])  
{  
  
    @autoreleasepool {  
          
        Student *stu=[[Student alloc]initWithAge:10];
        Book *book=[[Book alloc]initWithPrice:3.5];
        stu.book=book;

        [book release];
        [stu release];
    }  
    return 0;  
}             

運作結果:

2015-10-21 16:17:58.316 對象之間的記憶體管理[2049:303] book 3.500000被銷毀了

2015-10-21 16:17:58.318 對象之間的記憶體管理[2049:303] student 10被銷毀了

似乎沒有什麼問題,Student和Book都被釋放了。但是真的沒有什麼問題嗎?在實際開發中,我們通常把一些功能抽出來單獨寫一個方法。比如我們現在加一個test方法把Book的功能抽出來,大家再看看有沒有什麼問題。

#import <Foundation/Foundation.h>  
#import "Student.h"  
#import "Book.h"  
  
void test(Student *stu){  
    Book *book = [[Book alloc] initWithPrice:3.3];  
    stu.book = book;  
      
}  
  
  
  
int main(int argc, const char * argv[])  
{  
  
    @autoreleasepool {  
          
        Student *stu = [[Student alloc] initWithAge:10];  
          
        test(stu);  
          
        [stu release];  
          
    }  
    return 0;  
}  
           

大家有沒有發現問題?那我們再來運作一下

2015-10-21 16:27:58.396 對象之間的記憶體管理[2349:303] student 10被銷毀了

出現記憶體洩露了,book對象沒有被釋放,也就是說,book沒有被release

那麼有同學就會說了,在test方法中,在

stu.book=book;           

後面加上一個

[book release];           

不就可以解決了嗎?好像也沒有什麼問題,也沒有違背記憶體釋放的原則(誰建立誰釋放)。如果再增加一個新的需求,我們再給Student類增加一個方法叫readBook( ),用于列印出學生目前讀的書的價格。建立一個test1()方法,調用readBook()方法,然後再在main函數中調用 test1() 。我們來看一下

在Student.h檔案中聲明

-(void)readBook;           

在Student.m檔案中實作

#pragma  mark 讀書  
- (void)readBook{  
    NSLog(@"目前讀的書的價格是:%f",book.price);  //注意,這裡調用了book.prise
}            

mian.m函數修改如下:

#import <Foundation/Foundation.h>  
#import "Student.h"  
#import "Book.h"  
  
void test(Student *stu){  
    Book *book = [[Book alloc] initWithPrice:3.3];  
    stu.book = book;  
      
    [book release];  
      
}  
  
void test1(Student *stu){  
    [stu readBook];  
}  
  
  
  
int main(int argc, const char * argv[])  
{  
  
    @autoreleasepool {  
          
        Student *stu = [[Student alloc] initWithAge:10];  
          
        test(stu);  
          
        test1(stu);  
          
        [stu release];  
          
    }  
    return 0;  
}             

注意:

[stu readBook];           

調用的是

#pragma  mark 讀書  
- (void)readBook{  
    NSLog(@"目前讀的書的價格是:%f",book.price);  //注意,這裡調用了book.prise
} 
           

介紹這裡大家有沒有看出問題,是不是出現了問題?在test()中,我們已經把book釋放了,但是在test1()中,我們調用了readBook()方法,再次調用了book.price。既然book對象已經釋放了,已經不存在了,那我們通路不存在的記憶體對象會發生什麼錯誤?對,野指針錯誤!大家不要嫌我啰嗦,這個過程大家一定要明白,也一定要會分析對象的引用計數。要不然到最後會很麻煩的,經常出現一大堆莫名其妙的錯誤。

既然知道問題了,那問題就好解決了

解決方法:

我們可以使retain對象,使計數器+1,至于在哪裡retain,我們可以遵循一個原則:誰調用對象誰retain,Student的stu要使用book對象,那就讓Student自己在setBook中retain最好

-(void)setBook:(Book *)_book{
        book=[_book retain];   
}           

好,解決了野指針的問題,但是對于記憶體洩露還沒有解決,那麼我們在哪裡release呢?test1中?肯定不行,因為test1()中沒有retain,new或者alloc等建立對象的文法,release的話違背了我們“誰建立誰釋放“的原則。既然stu對象想使用book對象,你就應該在retain完成後釋放它,而不應該把它交給test1()去release。至于在Student對象的什麼時候釋放最好呢?當然是在stu對象結束退出之後,stu對象都不存在了,book對象就更沒有存在的必要了。是以在Student對象的dealloc中釋放掉book對象最合适。

#pragma mark 回收對象  
- (void)dealloc{  
      
    //釋放book對象  
    [book release];  
      
    NSLog(@"student %i 被銷毀了", age);  
      
    [super dealloc];  
}             

這個問題已經完美解決了,那我們來看下一個問題。假如我在test()方法中新建立一個對象book2,調用initWithPrice改變price的值

void test(Student *stu){  
    Book *book = [[Book alloc] initWithPrice:3.5];  
    stu.book = book;  
      
    [book release];  
      
    Book *book2 = [[Book alloc] initWithPrice:4.5];  
    stu.book = book2;  
    [book2 release];  
      
}            

其他都保持不動,為了友善大家閱讀,我們把main方法考過來

int main(int argc, const char * argv[])  
{  
  
    @autoreleasepool {  
          
        Student *stu = [[Student alloc] initWithAge:10];  
          
        test(stu);  
          
        test1(stu);  
          
        [stu release];  
          
    }  
    return 0;  
}             

這樣大家看一下,有沒有記憶體洩露,這個大家要根據引用計數進行分析,引用計數為0時進行釋放。那我們來運作一下

2013-10-21 17:43:01.519 對象之間的記憶體管理[2743:303]目前讀的書的價格是:4.500000

2013-10-21 17:43:01.521 對象之間的記憶體管理[2743:303] book 4.500000被銷毀了

2013-10-21 17:43:01.523 對象之間的記憶體管理[2743:303] student 10被銷毀了

book 3.500000這本書沒有被銷毀,為什麼沒有被銷毀?

大家先不要看下邊的,先自己想一想

大家學軟體程式設計一定要有自己獨立思考和分析問題的能力,這樣大家才能走的更高更遠,當然這隻是我個人的一點淺見,畢竟我也是一個想要飛的菜鳥。

那廢話就不多說了,我直接告訴大家

在stu.book=book2時,調用了setBook方法又進行了一次retain,這時候引用計數器為2,但是在最後釋放調用dealloc方法時,僅僅進行了一次release,是以最後引用計數器還是為1,造成了記憶體洩露。

是以我們可以改進一下:

- (void)setBook:(Book *)_book{  
    //先釋放舊的成員變量  
    [book release];  
      
    //再retain新傳進來的對象  
    book = [_book retain];  
}           

我們可以先把舊的成員釋放了,在retain新傳進來的對象,這樣就沒有問題了

但是仔細想想還是有一點小瑕疵,假如,我在test()方法中不小心多寫了一句stu.book=book;

void test(Student *stu){  
    Book *book = [[Book alloc] initWithPrice:3.3];  
    stu.book = book;  
      
    [book release];  
    stu.book = book;           

}

此時,stu.book 又再一次調用setter函數,在setter函數中release了book,問題是此時的book對象和_book對象時一樣的,book對象被釋放了(即_book指向的記憶體也不存在了),_book對象又再一次retian操作,就會造成野指針。

是以,要判斷一下傳進來的對象是否為目前對象,如果是目前對象的話就沒有必要再一次release,修改如下:

- (void)setBook:(Book *)_book{  
    if(_book != book){  
        //先釋放舊的成員變量  
        [book release];  
        //再retain新傳進來的對象  
        book = [_book retain];  
    }  
}             

完美的記憶體管理,這樣就是一段很完美的代碼了

視訊相關連結

http://pan.baidu.com/s/1jGLbz06

繼續閱讀