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)
空指针:没有指向任何东西的指针,存储的东西是nil、NULL、0,给空指针发送消息不会报错

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.错误写法

1》alloc之后调用了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

转载于:https://www.cnblogs.com/jianghg/p/4425658.html

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

  1. 银河麒麟操作系统基础学习笔记八

    which+命令 查看命令所在位置.比如 which ls,查看ls命令所在位置 whereis 文件或者命令或者目录 查看对应文件或命令或目录所在的位置 locate 关键字 找出系统中包含关键字的 ...

  2. python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑

    python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件? 当我们点开下载页时, 一 ...

  3. 1.C#基础学习笔记3---C#字符串(转义符和内存存储无关)

    技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com ------------------------------------- ...

  4. python3第三方库手册_python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑...

    python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件? 当我们点开下载页时, 一 ...

  5. Material Design风格神框架vuetify 学习笔记(八) 基础组件4 头像 扩展面板 消息条 评分...

    一. 头像 v-avatar v-avatar 组件通常用于显示循环用户个人资料图片. 此组件将允许您动态尺寸并添加响应图像.图标和文字的边框半径. <v-avatar color=" ...

  6. Python基础学习笔记之(一)

    Python基础学习笔记之(一) zouxy09@qq.com http://blog.csdn.net/zouxy09 前段时间参加微软的windows Azure云计算的一个小培训,其中Pytho ...

  7. Unity超基础学习笔记(二)

    Unity超基础学习笔记(二) 1. 基本数据类型的扩展 之前在K12中学习了一些基本的数据类型,实际上C#支持更多的数据类型.如下: 注意无符号整型数和有符号整型数的表示范围,例如: int 能表示 ...

  8. SCSI子系统基础学习笔记 (之UFS子系统) - 2.1UFS子系统初始化之ufs_qcom_probe

    目录 1. 前言 2.ufs_qcom_probe |- -ufshcd_alloc_host |- -ufshcd_init 参考文档 1. 前言 本专题我们开始学习SCSI子系统的相关内容.本专题 ...

  9. 8. SpringBoot基础学习笔记

    SpringBoot基础学习笔记 课程前置知识说明 1 SpringBoot基础篇 1.1 快速上手SpringBoot SpringBoot入门程序制作 1.2 SpringBoot简介 1.2.1 ...

  10. C基础学习笔记——01-C基础第02天(用户权限、VI操作、Linux服务器搭建)

    在学习C基础总结了笔记,并分享出来.有问题请及时联系博主:Alliswell_WP,转载请注明出处. 01-C基础第02天(用户权限.VI操作.Linux服务器搭建) 打开终端:ctrl+alt+t ...

最新文章

  1. PCL的PNG文件和计算点云重心
  2. jfinal框架增加微信jsapi支持
  3. 无插件Vim编程技巧
  4. 联想v3500存储Linux配置手册,OEL6.X IBM v3500存储多路径配置
  5. 12天学好C语言——记录我的C语言学习之路(Day 12)
  6. python观察日志(part24)--列表和numpy数组扁平化
  7. 30道经典SQL面试题讲解(1-10)
  8. 消息队列之推还是拉,RocketMQ 和 Kafka是如何做的?
  9. 【JZOJ4920】【NOIP2017提高组模拟12.10】降雷皇
  10. 论文阅读:Uncertainty-aware Joint Salient Object and Camouflaged Object Detection
  11. PostgreSQL【表】
  12. python 导入excel至oracle,Python读取Excel数据并将其导入Oracle数据库,导入到
  13. linux不要了装windows,从windows到linux —— 装linux吧,你不要怕!
  14. 魔域手游如何修改服务器id,魔域互通端游手游架设
  15. 数学建模与数学实验 (MATLAB)
  16. AutoHotKey实现百度云批量离线下载工具
  17. Windows10超级好用的虚拟机
  18. 黑苹果2k显示器开启hidpi_黑苹果无核显开启macOS-ipad随航功能
  19. 电脑PC端微信提示:你的微信崩溃次数较多,建议使用最新版本
  20. 利用报废主板制作SPD刷内存编程器座子

热门文章

  1. 怎么给字符串字段加索引?
  2. 普通索引和唯一索引,应该怎么选择?
  3. java getResourceAsStream方法
  4. Cmake构建_设置debug与release不同名字
  5. 时隔两年,PuTTY 喜提新版
  6. openSSH服务及其应用
  7. java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map
  8. Scanner--控制台输入
  9. Linux系统编程三:使用man查看帮助文档
  10. ArrayList的去重问题