您的位置:首页 > 移动开发 > Objective-C

IOS开发基础Object-C(08)—OC内存管理(2)-对象之间的内存管理

2015-11-22 07:37 615 查看
前几天,我们学习了内存管理的基本知识,了解了内存管理的基本原理。那么,今天我们来学习一下对象之间的内存管理,看看对象之间是如何进行内存管理的。首先,我们新建两个类: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息