Objective-C基础学习笔记(八)-内存管理-autorelease使用-property创建对象的内存管理-循环引用的内管管理

1.为什么要内存管理?

搞过嵌入式开发或底层开发的都知道,嵌入式系统的sdram或flash空间都非常有限,如果你的程序占用内存过大,擦做系统就会把你干掉。所以我们在开发应用程序时必须要控制好我们程序运行时所占用的系统资源。

2.OC是如何管理内存的?

1 》OC能管理的对象是:凡是继承于NSObject的类创建的对象。
2 》对象的创建过程:
前两篇文章中提到了对象的创建过程,再次说明一下,首先要调用alloc为这个对象去分配内存空间,然后调用init方法去初始化这个对象,当init方法不带任何参数时,就可以用new方法替代(这类似于先调用alloc,然后调用init),既然调用了alloc来分配内存的,就说明对象是存在于堆空间,这就要想办法让系统知道这块内存你已经用完了。
3 》OC中对对象的处理
每个OC对象都有一个四个字节引用计数器,是一个整数,表示“对象被引用的次数”,当引用计数器为0时系统回收,对象刚刚诞生时,计数器为1.要管理内存必须操作引用计数器.
4 》引用计数器的作用:
》当使用alloc、new、或者copy创建一个对象时,新对象的引用计数器默认就是1
》当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收,换句话说如果对象的计数器不为0,那么整个程序运行过程,它占用的内存不可能被回收除非整个程序已经退出.
5 》引用计数器的操作:
给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身);
给对象发送一条release消息,可使引用计数器值 -1
可以给对象发送retaincount消息获得当前引用计数器值

6 》对象的销毁:
1) 当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收
2) 当一个对象被销毁时系统会自动向对象发送一条dealloc消息
3) 一般会重写dealloc方法,在这里释放相关资源。dealloc就像对象的遗言
4) 一旦重写了dealloc方法就必须调用【super dealloc】并且在最后面调用
5) 不要直接调用dealloc方法
6) 一旦对象被回收了它占用的内存就不在可用,坚持使用会导致程序崩溃
 
7 》几个基本概念
僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能在使用
野指针:指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错  EXC_BAD_ACCESS
空指针:没有指向任何东西的指针,存储的东西是nilNULL0,给空指针发送消息不会报错

8》计数器操作使用示例:

Person类的声明:
1 #import <Foundation/Foundation.h>
2 
3 @interface Person : NSObject
4 {
5     int _age ;
6 }
7 @property int age;
8 @end

Person类的实现,为了便于观察对象是否被回收则重新实现了dealloc方法
 1 #import "Person.h"
 2 
 3 @implementation Person
 4 // 当一个Person对象被回收的时候,就会自动调用这个方法
 5 
 6 - (void)dealloc
 7 {
 8     NSLog(@"Person对象被回收");
 9     // super的dealloc一定要调用而且放在最后面
10     // EXD_BAD_ACCESS:访问了一块坏的内存(已被回收和释放的内存)
11     // 野指针
12     [super dealloc];
13 }
14 @end
计数器操作代码:
 1 int main()
 2 {
 3     /* 创建Person对象 */
 4     Person *p = [[Person alloc] init];
 5     /* 获取对像的计数器初始值 */
 6     NSInteger c = [p retainCount];
 7     NSLog(@"计数器初始值 %ld",c); //值为1
 8     
 9     // 2.调用retain方法,计数器加1,retain方法返回的是对象本身
10     [p retain];
11     c = [p retainCount];
12     NSLog(@"retain之后计数器值 %ld",c); //值为2
13     // 减为0才会被回收
14     
15     // 调用alloc 调用return 就必须有release
16     [p release]; // 减一操作
17     c = [p retainCount];
18     NSLog(@"release后计数器值 %ld",c); //值为1
19     [p release]; // 减一操作之后值为0,系统回收内存,执行dealloc方法,挂掉后面也不能在加1
20     
21     
22     // 给已经释放的对象发送了一条setAge消息: 报错
23     //p.age = 20;
24     
25     //防止野指针
26     p = nil;
27     
28     // 不能多次释放,产生野指针,OC里面指向僵尸对象(不可用内存)
29     // EXD_BAD_ACCESS:访问了一块坏的内存(已被回收和释放的内存)
30     // 野指针
31     [p release];//给空指针发消息不会报错,OC不存在空指针错误
32 
33     return 0;
34 }

运行结果:

2015-03-23 21:02:45.239 内存管理-引用计数器的操作[1521:161738]计数器初始值 1

2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] retain之后计数器值 2

2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] release后计数器值 1

2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] Person对象被回收

 
二.  set方法的内存管理

1. 一个对象被使用的实际意义有两种情况:
    1 》一个指针指向了创建的对象,例如对象创建的时候。
    2 》这个对象被一个类拥有,具体表现时使用了组合的方式,这个类的成员属性是另一个类的指针 ,这通常要通过set方法设置拥有的对象,此时就             要考虑内存管理的原则,具体的做法如下:
2. set方法的代码规范
   1 》基本类型的数据直接赋值
1 -(void)setAge:(int)age
2   {
3         _age = age;
4   }

   2 》OC对象类型,需要对计数器进行加一减一操作
 1  - (void)setCar:(Car *)car
 2 {
 3     // 1.先判断是否是新传来的对象
 4     if (_car != car)
 5     {
 6         // 2.旧对象做一次release
 7         [_car release]; // 第一次的话_car为空,
 8                         //对空对象release不会报错
 9         // 3.新对象做一次retain
10         _car = [car retain];
11     }
12 }

3.dealloc方法代码规范
1 // 1.一定要[super dealloc];而且放到最后面
2 // 2.对这个对象self(当前)所拥有的其他对象做一次release
3 - (void)dealloc;
4 {
5     [self->_car release]; 
6     //当你挂掉时必须让你拥有的对象的计数器减一
7      NSLog(@"%d 岁的人对象被回收",self->_age);
8     [super dealloc];
9 }

4. 使用@property自动创建对象的set方法时如何处理内存管理?
@property三大类参数:@property ()
1>>内存管理相关
  retain : release
旧值retain新值(适用于OC对象)
  assign :
直接赋值(默认:适用于非Oc对象类型)
  copy : release
旧值copy新值
  
例:
@property (retain) NSString *name
 

2>>是否要生成set方法
 
有种情况是某个变量是只读的不提供设置值的方法;
  readwrite:
同时生成set get方法(默认)
  readonly
只生成get声明、实现
 
例:
@property (readwrite,assign) int age;
 

3>>
多线程管理
 nonatomic:
性能高(一般就用这个)
 atomic
性能低(默认)
 
例:
@property (nonatomic,assign) int age;
 
4>>set
get方法名称
 
setter :决定set方法名称,一定要有冒号, 默认是setAge:
 getter:
决定get方法名称(一般用在BOOL类型)
 
常用于改变返回值BOOL类型的get方法的名称,一般以is开头.
 
例:@property (getter = abc,setter = setAbc: ) int weight;
 get
方法叫做
abc

 
返回值BOOL类型的方法名称,一般以is开头
 @property (getter = isRich) BOOL rich; // 这种在开发中常见
 
三.  autorelease
前面提到的对象管理原则,要我们要随时注意释放用alloc创建的对象,这样有一个问题,如果我们我们release之后,后面却又使用了此对象,会造成野指针错误,为了减轻我们的痛苦, autorelease应运而生,看名字就知道自动释放,具体用法看下文

1. autorelease基本用法

        1》会将对象放到一个自动释放池中

        2》挡自动释放池销毁时,会对池子里面的所有对象做一次release操作

        3》会返回对象本身

        4》调用完release后对象计数器并不会改变,只有到池子结束时才会release

        5》池子存储在栈中

  2. autorelease 好处

        1》不用在关心对象释放的时间

        2》不在关心什么时候调用release

    3. autorelease

        1》占用内存较大的对象不要随便使用Release

        2》占用内存较小的对象使用autorelease,没有太大影响

    4.错误写法

        1alloc之后调用了autorelease,又调用了release

 1  @autoreleasepool
 2 
 3 {
 4 
 5     // 调用两次autorelease,就会调用两次release出现野指针错误
 6 
 7     Person *p = [[[[Person alloc] init] autorelease]; autorelease];
 8 
 9      [p release];
10 
11 }

 

        2》连续调用对次release

         

1 Person *p = [[[[Person alloc] init] autorelease] autorelease];

 

    5. 自动释放池

       1》在IOS程序运行过程中会创建无数的池子。这些池子都已栈的方式存在先进后出

       2》当一个对像调用autorelease方法时,会将这个对象放到栈顶释放池

autorelease使用示例:
Person类的声明
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,assign) int age;
@end

 

Person类的实现;
1 #import "Person.h"
2 
3 @implementation Person
4 - (void)dealloc
5 {
6     NSLog(@"Person 对象被回收");
7     [super dealloc];
8 }
9 @end

 

autorelease使用:
 1 int main()
 2 {
 3     // autorelease 返回对象本身
 4     // 调用完release后对象计数器并不会改变,只有到池子结束时才会release
 5     // sutorelease 作用,会将对象放到自动释放池中,当释放池被销毁时会将
 6     // 池子中所有的对象做一次release操作,池子在哪
 7     @autoreleasepool
 8     {// { 代表释放池开始
 9         Person *p =[[[Person alloc] init] autorelease];
10         p.age = 10;
11         
12         
13         // 可创建n多个自动释放池
14         @autoreleasepool
15         {
16             Person *p =[[[Person alloc] init] autorelease];
17             p.age = 20;
18         
19         }
20         //[p release];
21     
22     }// {代表释放池销毁
23 
24     return 0;
25 }

 

运行结果:

2015-03-23 21:52:55.539 autorelease[1627:174759] Person对象被回收

2015-03-23 21:52:55.540 autorelease[1627:174759] Person对象被回收

可见在地址池结束时确实释放了我们创建的对象。

四.循环引用的内管管理

1. 何为循环引用:
来一个例子:人有身份证,身份证上有人这个属性,表明了一个人的身份,如果抽象成面想对象就是Person类拥有Card类,但同时Card类又拥有Person类这就是循环引用。

2.如果按照正常的内存管理原则循环引用会产生什么问题?
说了按照正规的内存管理去处理,试试便知:
Person类的声明:
 1 #import <Foundation/Foundation.h>
 2 
 3 // 不用import
 4 // @class 仅仅告诉编译器card是一个类
 5 @class Card;
 6 
 7 @interface Person : NSObject
 8 
 9 @property (nonatomic,retain) Card * card;
10 @end

 

3.关于@class 

1》使用 @class 类名; 就可以引用一个类,说明一下它是一个类

2》 @class   和#import的区别

 #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;

@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息.

如果有n多个文件都#import了同一个头文件,那么如果最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率肯定是很慢的,而使用@class方式就不会出现这种问题

在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类


Person类的实现(使用了#import)
 1 #import "Person.h"
 2 // 在.m文件中用到才包含
 3 #import "Card.h"
 4 
 5 @implementation Person
 6 - (void)dealloc
 7 {
 8     NSLog(@"Person 对象被回收");
 9     // 回收之前将拥有的对象计数器减一
10     [_card release];
11     [super dealloc];
12 }
13 @end

 

Card类的声明
 1 #import <Foundation/Foundation.h>
 2 
 3 // 仅在声明时告诉编译器这是一个类
 4 @class Person;
 5 
 6 @interface Card : NSObject
 7 /* 生分证上的 人属性*/
 8 // 由于Person类是对象所以使用retain参数
 9 @property (nonatomic,retain) Person *person;
10 @end

 

Card类的实现
1 - (void)dealloc
2 {
3     NSLog(@"Card对象被回收");
4     // 回收之前将拥有的对象计数器减一
5     [_person release];
6     [super dealloc];
7 }
8 @end

会引起什么问题?

 

 1 #import <Foundation/Foundation.h>
 2 #import "Person.h"
 3 #import "Card.h"
 4 int main()
 5 {
 6     // 创建Person类的对象
 7     Person *p = [[Person alloc] init];
 8     // 创建Card 类的对象
 9     Card *c = [[Card alloc] init];
10     
11     // 使Person对象拥有Card对象
12     p.card = c;
13     c.person = p;
14     // 使Card对象拥有Person对象
15     
16     [c release];
17     [p release];
18 
19     return 0;
20 }
运行结果:

上面的代码看似没有什么问题,但是没有调用person对像和Card对象的dealloc方法,说明两个对象都没有被释放?

分析原因:

p、c创建时计数器都为1,执行完p.card = c,之后c的计数器为2,同理c.peron= p之后p的计数器也为2,继续执行后面的[c release],c的计数器为1,不为空就不会执行card的dealloc方法,就不会使他拥有的person对象计数器减一,那么p的计数器还是2,之后执行[p relase]p计数器变为1,也不会执行person的dealloc,这样程序运行完毕,哪个对象都没有释放。

3.解决方案

当两端互相引用时,应该一端用retain、一端用assign

先修改card端修改之后如下:

 1  
 2 
 3 #import <Foundation/Foundation.h>
 4 
 5 // 仅在声明时告诉编译器这是一个类
 6 @class Person;
 7 
 8 @interface Card : NSObject
 9 /* 生分证上的 人属性*/
10 
11 // 由于Person类是对象所以使用retain参数
12 //@property (nonatomic,retain) Person *person;
13 // 修改之后的代码
14 @property (nonatomic,assign) Person *person;
15 @end
16 
17 #import "Card.h"
18 #import "Person.h"
19 @implementation Card
20 
21 - (void)dealloc
22 {
23     NSLog(@"Card对象被回收");
24     // 回收之前将拥有的对象计数器减一
25     // [_person release];
26     [super dealloc];
27 }
28 @end
main函数不变运行结果:

2015-03-23 22:38:42.620 循环引用[1729:186447] Person对象被回收

2015-03-23 22:38:42.621 循环引用[1729:186447] Card对象被回收


五.总结OC的内存管理原则:

1》原则分析
    只要还有人在用某个对象,那么这个对象就不会被回收,只要你想用这个对象,就调用retain让对象的计数器+1,刚创建的时候则不用,当你不再使用这个对象时,就要调用release让对象计数器-1
2》谁创建,谁release:
    如果你通过alloc、new、或者[mutable ]copy 来创建一个对象,那么你必须调用 release 或者autorelease
   换句话说,不是你创建的,就不用你去[auto]release
3》谁retain谁release
   只要你调用了retain,无论对象是谁生成的,你都要调用release



 

 

posted @ 2015-03-23 18:32  HugoJiang  阅读(226)  评论(0编辑  收藏  举报