ios内存管理2-对象之间的内存管理
同之前一样,新建一个基于命令行的工程,在新建一个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
//
// 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类的编写:
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
//
// 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
//
// 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被销毁了
这样看来好像一切都没问题,不会造成任何的内存泄露,,,在比较小的程序中没什么问题,但是在实际开发中,在稍大的项目中,这样写就有很大的隐患。在真实的开发环境中,我们往往要写很多的方法函数,为了测试,现在我们增加一个test( )函数,在test函数中实现对Book类的调用,如下:
//
// 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];
可以,问题解决了,,但是如果我想增加一下需求,假如现在我又有一个方法test1( ),用它来操作Student,,先我们再给Student类增加一个方法叫readBook( ),用于打印出学生当前读的书的价格。然后再在main函数中调用 test1() 这个函数
Student.h增加如下声明:
- (void)readBook;
Student.m进行实现:
#pragma mark - 公共方法
#pragma mark 读书
- (void)readBook{
NSLog(@"当前读的书的价格是:%f",book.price);
}
mian.m函数修改如下:
//
// 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;
}
请注意test1()函数里的
[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];
}
以上虽然解决了野指针的问题,,但是却没有release,,我们不在test1()中进行release,,是因为test1()函数中并没有明显的alloc或new一个对象,突然间出现一个release显得很唐突,2二来不符合内存管理的法则(
谁创建,谁释放),既然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];
}
这样写同时也保证了内存管理的法则:
谁retain的,谁就有责任release。
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];
}
其它不改动,,那么请考虑一下,现在会没有内存泄露(
通过引用计数器最后是否为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,代码如下:
- (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;
此时,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];
}
}
至此,,对代码的内存管理就真的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
ios内存管理2-对象之间的内存管理的更多相关文章
- Java连载34-对象的内存分析、对象之间建立关系
一.内存分析 代码:引用可以是局部变量也可以是成员变量 public class Test1{ public static void main(String[] args){ User u = new ...
- Java对象的内存布局以及对象所需内存大小计算详解
1. 内存布局 在HotSpot虚拟机中,对象的内存布局可以分为三部分:对象头(Header). 实例数据(Instance Data)和对齐填充(Padding). 1) 对象头(Header): ...
- 浅谈Java虚拟机内存中的对象创建,内存布局,访问定位
参考于 深入理解Java虚拟机 这里介绍HotSpot虚拟机(自带的虚拟机) 1.对象的创建 对于程序员来说,创建对象的方法: User user1 = new User(); User user2 ...
- 深入理解Java虚拟机(二)——HotSpot对象创建、内存、访问
对象的创建 虚拟机遇到一条字节码new指令时,开始对象创建过程. 首先去检查这个指令的参数是否能在常量池定位到一个类的符号引用: 检查这个符号引用代表的类是否已被加载.解析和初始化,如果没有就必须执行 ...
- iOS 非ARC基本内存管理系列 2-多对象内存管理(3) 利用@property来自动管理内存
iOS 基本内存管理-多对象内存管理(2)中可以看到涉及到对象的引用都要手动管理内存:每个对象都需要写如下代码 // 1.对要传入的"新车"对象car和目前Person类对象所拥有 ...
- Objective-C:Objective-C 和 Core Foundation 对象相互转换的内存管理
Objective-C 和 Core Foundation 对象相互转换的内存管理 iOS允许Objective-C 和 Core Foundation 对象之间可以轻松的转换,拿 NSString ...
- IOS ARC内存管理,提高效率避免内存泄露
本文转载至 http://blog.csdn.net/allison162004/article/details/38756263 Cocoa内存管理机制 (1)当你使用new.alloc.copy方 ...
- iOS面试题05-父子控制器、内存管理
内存管理.父子控制器面试题 1.建立父子关系控制器有什么用 回答:1>监听屏幕选中 2>如果想拿到你当前的很小的一个控制器所在的导航控制器必须要跟外面比较大的控制器建立父子关系,才能一层一 ...
- Objective-C 和 Core Foundation 对象相互转换的内存管理总结
本文转载至 http://blog.csdn.net/allison162004/article/details/38756649 OS允许Objective-C 和 Core Foundation ...
随机推荐
- myeclipse 8.6 插件安装之SVN
在这里我要说明一点,myEclipse 8.6的插件安装和之前的版本可能会有一些区别,下面是SVN插件的安装: 1.从官网下载site-1.6.13.zip文件,网址是:subclipse.tigri ...
- ASP.NET MVC3 Razor视图引擎-基础语法
I:ASP.NET MVC3在Visual Studio 2010中的变化 在VS2010中新建一个MVC3项目可以看出与以往的MVC2发生了很明显的变化. 1.ASP.NET MVC3必要的运行环境 ...
- 转: 用css把图片转为灰色图
小tip: 使用CSS将图片转换成黑白(灰色.置灰) by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.co ...
- border-radius.htc为ie6-8实现圆角
~~圆角是比较常用的css3属性,但是ie6-8并不支持圆角,可用border-radius.htc html组件实现圆角, border-radius.htc内部应用vml进行了重绘 border ...
- Maven2的配置文件settings.xml(转)
http://maven.apache.org/settings.html简介: 概览当Maven运行过程中的各种配置,例如pom.xml,不想绑定到一个固定的project或者要分配给用户时,我们使 ...
- HDU 2227 Find the nondecreasing subsequences (线段树)
Find the nondecreasing subsequences Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/3 ...
- Android Dalvik 虚拟机
简介 Android 平台虽然是使用java语言来开发应用程序,但Android程序却不是运行在标准java虚拟机上的.谷歌专门为Android平台设计了一套虚拟机来运行Android程序.它就是Da ...
- poj 1200 Crazy Search(hash)
题目链接:http://poj.org/problem?id=1200 思路分析:从数据来看,该题目使用线性时间算法,可见子串的比较是不可能的:使用hash可以在常数时间内查找,可以常数时间内判重, ...
- C语言中没有main函数生成可执行程序的几种方法
1.define预处理指令 这种方式很简单,只是简单地将main字符串用宏来代替,或者使用##拼接字符串.示例程序如下: #include <stdio.h> #define begin ...
- 记录一次SQL查询语句
以前发现比较经典的句子,都是记录在电脑上,我今天想搬到博客上,在我看来,写博客真的是一件非常头疼的事,它是内心的一道坎,我必须得跨过它. CREATE TABLE t_jeff ( id int NO ...