ios内存管理2-对象之间的内存管理

转自于http://blog.csdn.net/chaoyuan899

同之前一样,新建一个基于命令行的工程,在新建一个Student类和一个Book类

编写如下代码:

Student.h

  1. //  
  2. //  Student.h  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10. #import "Book.h"  
  11.   
  12. @interface Student : NSObject  
  13. {  
  14.     int age;  
  15.     Book *book;  
  16. }  
  17.   
  18. @property int age;  
  19.   
  20. - (id) initWithAge:(int)_age;  
  21. @property Book *book;  
  22.   
  23. @end  

Student.m
  1. //  
  2. //  Student.m  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import "Student.h"  
  10.   
  11. @implementation Student  
  12.   
  13. @synthesize age, book;//在Xcode4.5及以后的版本中,这一句可以省略,编译器会自动实现getter和setter方法  
  14.   
  15. #pragma mark - 生命周期方法   
  16. #pragma mark 构造方法  
  17. - (id)initWithAge:(int)_age{  
  18.     if(self = [super init])  
  19.         age = _age;  
  20.       
  21.     return self;  
  22. }  
  23.   
  24. #pragma mark 回收对象  
  25. - (void)dealloc{  
  26.       
  27.     NSLog(@"student %i 被销毁了", age);  
  28.       
  29.     [super dealloc];  
  30. }  
  31.   
  32.   
  33. @end  

Book类的编写:

Book.h

  1. //  
  2. //  Book.h  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10.   
  11. @interface Book : NSObject  
  12. {  
  13.     float price;  
  14. }  
  15.   
  16. @property float price;  
  17.   
  18. - (id)initWithPrice:(float)_price;  
  19.   
  20.   
  21. @end  

Book.m
  1. //  
  2. //  Book.m  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import "Book.h"  
  10.   
  11. @implementation Book  
  12.   
  13. @synthesize price;//在Xcode4.5及以后的版本中,这一句可以省略,编译器会自动实现getter和setter方法  
  14.   
  15. - (id)initWithPrice:(float)_price{  
  16.     if(self = [super init])  
  17.         price = _price;  
  18.       
  19.     return self;  
  20. }  
  21.   
  22. - (void)dealloc{  
  23.     NSLog(@"book %f 被销毁了", price);  
  24.       
  25.     [super dealloc];//不要忘了这一句,而且是放在最后的。  
  26. }  
  27.   
  28. @end  

main.m
  1. //  
  2. //  main.m  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10. #import "Student.h"  
  11. #import "Book.h"  
  12.   
  13. int main(int argc, const char * argv[])  
  14. {  
  15.   
  16.     @autoreleasepool {  
  17.           
  18.         Student *stu = [[Student alloc] initWithAge:10];  
  19.           
  20.         Book *book = [[Book alloc] initWithPrice:3.3];  
  21.           
  22.         stu.book = book;  
  23.           
  24.         [book release];  
  25.         [stu release];  
  26.           
  27.     }  
  28.     return 0;  
  29. }  

运行结果如下:

2013-08-26 18:17:58.316内存管理2-对象之间的内存管理[2049:303] book 3.300000被销毁了

2013-08-26 18:17:58.318内存管理2-对象之间的内存管理[2049:303] student 10被销毁了


    这样看来好像一切都没问题,不会造成任何的内存泄露,,,在比较小的程序中没什么问题,但是在实际开发中,在稍大的项目中,这样写就有很大的隐患。在真实的开发环境中,我们往往要写很多的方法函数,为了测试,现在我们增加一个test( )函数,在test函数中实现对Book类的调用,如下:
  1. //  
  2. //  main.m  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10. #import "Student.h"  
  11. #import "Book.h"  
  12.   
  13. void test(Student *stu){  
  14.     Book *book = [[Book alloc] initWithPrice:3.3];  
  15.     stu.book = book;  
  16.       
  17. }  
  18.   
  19.   
  20.   
  21. int main(int argc, const char * argv[])  
  22. {  
  23.   
  24.     @autoreleasepool {  
  25.           
  26.         Student *stu = [[Student alloc] initWithAge:10];  
  27.           
  28.         test(stu);  
  29.           
  30.         [stu release];  
  31.           
  32.     }  
  33.     return 0;  
  34. }  


点击运行,结果如下:

2013-08-26 18:48:40.125内存管理2-对象之间的内存管理[2337:303] student 10被销毁了


很明显,,出现了内存泄露,book对象没有被释放。此时,book的引用计数器为1。(OC通过引用计数器来管理内存,当引用计数器为0时销毁对象进行内存回收)

那么应该怎样解决呢?有人说直接在

  1. stu.book = book;  

后面直接加一句

  1. [book release];  

可以,问题解决了,,但是如果我想增加一下需求,假如现在我又有一个方法test1( ),用它来操作Student,,先我们再给Student类增加一个方法叫readBook( ),用于打印出学生当前读的书的价格。然后再在main函数中调用 test1() 这个函数

Student.h增加如下声明:

  1. - (void)readBook;  


Student.m进行实现:

  1. #pragma  mark - 公共方法  
  2. #pragma  mark 读书  
  3. - (void)readBook{  
  4.     NSLog(@"当前读的书的价格是:%f",book.price);  
  5. }  

mian.m函数修改如下:
  1. //  
  2. //  main.m  
  3. //  内存管理2-对象之间的内存管理  
  4. //  
  5. //  Created by Rio.King on 13-8-26.  
  6. //  Copyright (c) 2013年 Rio.King. All rights reserved.  
  7. //  
  8.   
  9. #import <Foundation/Foundation.h>  
  10. #import "Student.h"  
  11. #import "Book.h"  
  12.   
  13. void test(Student *stu){  
  14.     Book *book = [[Book alloc] initWithPrice:3.3];  
  15.     stu.book = book;  
  16.       
  17.     [book release];  
  18.       
  19. }  
  20.   
  21. void test1(Student *stu){  
  22.     [stu readBook];  
  23. }  
  24.   
  25.   
  26.   
  27. int main(int argc, const char * argv[])  
  28. {  
  29.   
  30.     @autoreleasepool {  
  31.           
  32.         Student *stu = [[Student alloc] initWithAge:10];  
  33.           
  34.         test(stu);  
  35.           
  36.         test1(stu);  
  37.           
  38.         [stu release];  
  39.           
  40.     }  
  41.     return 0;  
  42. }  

请注意test1()函数里的
  1. [stu readBook];  

这一句,,这一句访问的是下面这个函数



  1. - (void)readBook{  
  2.     NSLog(@"当前读的书的价格是:%f",book.price);  
  3. }  
而这里的book对象已经在test()函数中已经被释放掉了,不存在该内存区域了,再去访问的话就会造成野指针了。那能不能把test( )函数中的 [ book release ]放到test1( ) 函数中,,,这样的思路是正确的,但是有很多不好的地方,1、不符合我们之前说过的内存管理的原则( 谁创建,谁释放);2、还要把 stu 对象传递到test1( ),当代码一多的时候就会很麻烦且使代码显得冗余,所以不用这种方法。

所以不要用这种方法

解决办法:可以用 retain 对象,使引用计数器加1。至于在什么地方retain呢?有一个原则是谁想使用该对象,谁就去retain。这里是Student 的stu对象要使用book对象,所以要Student自己去retain,在stu的setBook函数retain是最好的。(原则:谁想使用book对象,谁就去retain)

直接在setBook()方法中retain,,注意,不要在test()方法中retain,,why??还是上面的原则。

  1. - (void)setBook:(Book *)_book{  
  2.     book = [_book retain];  
  3. }  

以上虽然解决了野指针的问题,,但是却没有release,,我们不在test1()中进行release,,是因为test1()函数中并没有明显的alloc或new一个对象,突然间出现一个release显得很唐突,2二来不符合内存管理的法则( 谁创建,谁释放),既然stu对象想使用book对象,你就应该在retain完成后释放它,而不应该把它交给test1()去release。至于在Student对象的什么时候释放最好呢?当然是在stu对象结束退出之后,stu对象都不存在了,book对象就更没有存在的必要了。所以在Student对象的dealloc中释放掉book对象最合适。

代码如下:

  1. #pragma mark 回收对象  
  2. - (void)dealloc{  
  3.       
  4.     //释放book对象  
  5.     [book release];  
  6.       
  7.     NSLog(@"student %i 被销毁了", age);  
  8.       
  9.     [super dealloc];  
  10. }  

这样写同时也保证了内存管理的法则: 谁retain的,谁就有责任release。

OK,问题完美解决。


还有一个问题,现在我test()里面又新增加了一个对象book2,调用initWithPrice改变price的值,,,调用setter改变变量的值这在实际编程中是经常遇到的。代码如下:

  1. void test(Student *stu){  
  2.     Book *book = [[Book alloc] initWithPrice:3.3];  
  3.     stu.book = book;  
  4.       
  5.     [book release];  
  6.       
  7.     Book *book2 = [[Book alloc] initWithPrice:4.5];  
  8.     stu.book = book2;  
  9.     [book2 release];  
  10.       
  11. }  

其它不改动,,那么请考虑一下,现在会没有内存泄露( 通过引用计数器最后是否为0判断)呢???

运行结果是:

2013-08-26 20:43:01.512内存管理2-对象之间的内存管理[2743:303]当前读的书的价格是:4.500000

2013-08-26 20:43:01.514内存管理2-对象之间的内存管理[2743:303] book 4.500000被销毁了

2013-08-26 20:43:01.515内存管理2-对象之间的内存管理[2743:303] student 10被销毁了

以上运行结果可以看到,book 3.300000这本书没有被销毁,内存泄露!!

原因在于,当stu.book = book2这句调用setter方法的时候又retain了一次,所以引用计数器又加了1,变成2了,而最后在dealloc中只release了一次,所以计数器变为1,造成内存泄露。

改进:

在setter中release,代码如下:

  1. - (void)setBook:(Book *)_book{  
  2.     //先释放旧的成员变量  
  3.     [book release];  
  4.       
  5.     //再retain新传进来的对象  
  6.     book = [_book retain];  
  7. }  

运行结果如下:

2013-08-26 21:20:38.979内存管理2-对象之间的内存管理[2886:303] book 3.300000被销毁了

2013-08-26 21:20:38.981内存管理2-对象之间的内存管理[2886:303]当前读的书的价格是:4.500000

2013-08-26 21:20:38.982内存管理2-对象之间的内存管理[2886:303] book 4.500000被销毁了

2013-08-26 21:20:38.982内存管理2-对象之间的内存管理[2886:303] student 10被销毁了


到这里,,,一切看起来似乎是挺完美的了,,其实还是有一点小缺陷。假如我在test()函数编码的时候不小心写多了一句stu.book = book ,即

  1. stu.book = book;  
  2.   
  3. [book release];  
  4.   
  5. stu.book = book;  

此时,stu.book 又再一次调用setter函数,在setter函数中release了book,问题是此时的book对象和_book对象时一样的,book对象被释放了(即_book指向的内存也不存在了),_book对象又再一次retian操作,就会造成野指针。

所以,要判断一下传进来的对象是否为当前对象,如果是当前对象的话就没有必要再一次release,修改如下:

  1. - (void)setBook:(Book *)_book{  
  2.     if(_book != book){  
  3.         //先释放旧的成员变量  
  4.         [book release];  
  5.         //再retain新传进来的对象  
  6.         book = [_book retain];  
  7.     }  
  8. }  

至此,,对代码的内存管理就真的perfect了。

运行结果如下:


2013-08-26 22:00:53.010内存管理2-对象之间的内存管理[3034:303] book 3.300000被销毁了

2013-08-26 22:00:53.013内存管理2-对象之间的内存管理[3034:303]当前读的书的价格是:4.500000

2013-08-26 22:00:53.015内存管理2-对象之间的内存管理[3034:303] book 4.500000被销毁了

2013-08-26 22:00:53.016内存管理2-对象之间的内存管理[3034:303] student 10被销毁了


以上源代码下载地址:

http://download.csdn.net/detail/chaoyuan899/6015927
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值