前幾天,我們學習了記憶體管理的基本知識,了解了記憶體管理的基本原理。那麼,今天我們來學習一下對象之間的記憶體管理,看看對象之間是如何進行記憶體管理的。首先,我們建立兩個類: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