前几天,我们学习了内存管理的基本知识,了解了内存管理的基本原理。那么,今天我们来学习一下对象之间的内存管理,看看对象之间是如何进行内存管理的。首先,我们新建两个类: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
#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.h
#import <Foundation/Foundation.h> @interface Book : NSObject { float price; } @property float price; - (id)initWithPrice:(float)_price; @end
#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
#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 }
#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]; }
#pragma mark 回收对象 - (void)dealloc{ //释放book对象 [book release]; NSLog(@"student %i 被销毁了", age); [super dealloc]; }
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; }
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被销毁了
大家先不要看下边的,先自己想一想
大家学软件编程一定要有自己独立思考和分析问题的能力,这样大家才能走的更高更远,当然这只是我个人的一点浅见,毕竟我也是一个想要飞的菜鸟。
那废话就不多说了,我直接告诉大家
在stu.book=book2时,调用了setBook方法又进行了一次retain,这时候引用计数器为2,但是在最后释放调用dealloc方法时,仅仅进行了一次release,所以最后引用计数器还是为1,造成了内存泄露。
所以我们可以改进一下:
- (void)setBook:(Book *)_book{ //先释放旧的成员变量 [book release]; //再retain新传进来的对象 book = [_book 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