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

你可能感兴趣的:(内存管理,oc,retain,release,对象内存管理)