前几天,我们学习了内存管理的基本知识,了解了内存管理的基本原理。那么,今天我们来学习一下对象之间的内存管理,看看对象之间是如何进行内存管理的。首先,我们新建两个类: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类的声明与实现
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被销毁了
大家先不要看下边的,先自己想一想
大家学软件编程一定要有自己独立思考和分析问题的能力,这样大家才能走的更高更远,当然这只是我个人的一点浅见,毕竟我也是一个想要飞的菜鸟。
那废话就不多说了,我直接告诉大家
在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