同之前一样,新建一个基于命令行的工程,在新建一个Student类和一个Book类
编写如下代码:
Student.h
// // Student.h // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #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 // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #import "Student.h" @implementation Student @synthesize age, book;//在Xcode4.5及以后的版本中,这一句可以省略,编译器会自动实现getter和setter方法 #pragma mark - 生命周期方法 #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
// // Book.h // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #import <Foundation/Foundation.h> @interface Book : NSObject { float price; } @property float price; - (id)initWithPrice:(float)_price; @end
// // Book.m // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #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 // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #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.3]; stu.book = book; [book release]; [stu release]; } return 0; }
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被销毁了
// // main.m // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #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; }
点击运行,结果如下:
2013-08-26 18:48:40.125内存管理2-对象之间的内存管理[2337:303] student 10被销毁了
很明显,,出现了内存泄露,book对象没有被释放。此时,book的引用计数器为1。(OC通过引用计数器来管理内存,当引用计数器为0时销毁对象进行内存回收)
那么应该怎样解决呢?有人说直接在
stu.book = book;
后面直接加一句
[book release];
Student.h增加如下声明:
- (void)readBook;
Student.m进行实现:
#pragma mark - 公共方法 #pragma mark 读书 - (void)readBook{ NSLog(@"当前读的书的价格是:%f",book.price); }
// // main.m // 内存管理2-对象之间的内存管理 // // Created by Rio.King on 13-8-26. // Copyright (c) 2013年 Rio.King. All rights reserved. // #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];
这一句,,这一句访问的是下面这个函数
- (void)readBook{ NSLog(@"当前读的书的价格是:%f",book.price); }而这里的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??还是上面的原则。
- (void)setBook:(Book *)_book{ book = [_book retain]; }
代码如下:
#pragma mark 回收对象 - (void)dealloc{ //释放book对象 [book release]; NSLog(@"student %i 被销毁了", age); [super dealloc]; }
OK,问题完美解决。
还有一个问题,现在我test()里面又新增加了一个对象book2,调用initWithPrice改变price的值,,,调用setter改变变量的值这在实际编程中是经常遇到的。代码如下:
void test(Student *stu){ Book *book = [[Book alloc] initWithPrice:3.3]; stu.book = book; [book release]; Book *book2 = [[Book alloc] initWithPrice:4.5]; stu.book = book2; [book2 release]; }
运行结果是:
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,代码如下:
- (void)setBook:(Book *)_book{ //先释放旧的成员变量 [book release]; //再retain新传进来的对象 book = [_book retain]; }
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 ,即
stu.book = book; [book release]; stu.book = book;
所以,要判断一下传进来的对象是否为当前对象,如果是当前对象的话就没有必要再一次release,修改如下:
- (void)setBook:(Book *)_book{ if(_book != book){ //先释放旧的成员变量 [book release]; //再retain新传进来的对象 book = [_book retain]; } }
运行结果如下:
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