解铃还须系铃人

--1--内存管理的原理及分类
1.1 内存管理的原理
1.2 内存管理的分类
--2--手动内存管理
2.1 关闭ARC的方法
2.2 手动管理(MRC)快速入门
--3-- 内存管理的原则
3.1 内存管理的原则
3.2 内存管理研究的内容
--4-- 单对象内存管理
4.1 单个对象的野指针问题
4.2 避免使用僵尸对象的方法
4.3 对象的内存泄漏
--5-- 多个对象内存管理
5.1 多个对象的野指针问题
5.2 多个对象内存泄漏问题
--6-- set方法内存管理
6.1 set方法存在的问题
6.2 不同类型的setter写法
--7-- autorelease
7.1 autorelease是什么
7.2 为什么会有autorelease
7.3 autorelease基本用法
7.4 autorelease的原理
7.5 autorelease什么时候被释放
7.6 autorelease使用注意

--------------------------------------

【写在开头】

『使用这个标题,“解铃还须系铃人”好像有点不正式。但这里,只是想突出一个内存管理的原则:“谁创建,谁释放”。iOS的内存管理和Java等语言的垃圾回收机制不同,Java的垃圾回收机制是运行时的特性,由jvm去回收释放内存。这里不谈Java,回到iOS的内存管理,目前创建项目默认就是ARC机制,而以前是MRC机制,需要程序员自己手动去回收释放内存,注意ARC和MRC都是编译器的特性,一个移动设备的内存是有限的,所以学习iOS的内存管理非常必要。

OC内存管理的范围:

管理任何继承自NSObject的对象,对其他的基本数据类型无效。

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,如局部变量最主要存放在栈中。而对象存储在堆中,当代码块结束时,这个代码块中涉及的所有的局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,就会造成内存泄漏。

下面的内存管理内容主要是MRC机制』

--1--内存管理的原理及分类

1.1 内存管理的原理

1)对象的所有权及引用计数

  对象所有权概念:

  任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会继续存在。

  Cocoa所有权策略:

  任何自己创建的对象都归自己所有,可以使用名字以“alloc"或”new“开头或名字中包含”copy“的方法创建对象,可以使用retain来获得一个对象的所有权。

  对象的引用计数器:

  每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。

2)引用计数器的作用

  引用计数器(retainCount):标志被引用的次数,存储当前对象有几个使用者,是判断对象是否回收的依据。

  (例外:对象值为nil时,引用计数器为0,但不回收空间,因为没有分配空间)

3)对引用计数器的操作:

  retain消息:使计数器+1,方法返回对象本身

  release消息:使计数器-1

  retainCount消息:获得当前对象的retainCount的值

4)对象的销毁:

  当retainCount为0时,对象被销毁,内存被回收。对象被销毁时,系统自动向对象发送一条dealloc消息,一般会重写dealloc消息,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。

  一旦重写了dealloc方法就必须调用[super dealloc](注意不能直接调用dealloc方法)。

  一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。

注意:

1)如果对象的计数器不为0,那么在整个程序运行过程中,它占用的内存就不可能被回收(除非整个程序已经退出)

2)任何一个对象,刚创建的时候,引用计数器都为1,当使用alloc、new或者coppy创建一个对象时,对象的引用计数器默认是1

从此可以看出:

回收一个对象与否的标记就是引用计数器是否为0.

1.2 内存管理的分类

OC内存管理分为3类:

  1)Manual Reference Counting (MRC,手动管理,在开发iOS4.1之前的版本的项目时,需要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)

  2)Automatic Reference Counting(ARC,自动引用计数,iOS4.1之后推出)

  3)garbage collection(垃圾回收)IOS不支持垃圾回收

ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存。

--2--手动内存管理

2.1 关闭ARC

创建一个项目时,默认是ARC的(自动内存管理),手动把ARC项目改成MRC项目的方法之一如下:

a.选中项目,此时Xcode右侧会出现如图设置信息

b. 在同时选中Build Settings和Levels时在右侧搜索框搜索auto,此时会出现ARC设置

c.把ARC设置中目标target下的Yes设置为No,其他相应的Yes也变为No,此时就是MRC管理

2.2 手动管理(MRC)快速入门

内存管理的关键是判断对象是否被回收?

重写dealloc方法,

代码规范

1)要调用父类的dealloc方法[supper dealloc],而且要放到最后,意义是:先释放子类占用的空间再释放父类占用的空间

2)对self(当前)所拥有的其他对象做一次release操作

 -  (void) dealloc

{

[_car release]; //对象关联的对象

[super dealloc];

}

注意:

一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针)为了防止调用出错,也可以将“野指针”指向nil(0);

--3-- 原则

3.1 内存管理的原则

1)原则:

  如果对象还有使用者,就不应该回收;

  如果你想使用这个对象,那么就应该让这个对象的引用计数器+1;

  当你不想使用这个对象时,应该让对象的引用计数器-1;

2)谁创建,谁release

  1.如果通过alloc, new, coppy来创建一个对象,那么久必须调用对应的release或者autorelease方法

  2.谁创建谁负责

3)谁retain,谁release

  只要你调用了retain,无论这个对象是如何生成的,你都需调用release

总结:

有始有终,有加就应该有减,让某个对象计数器加1,就应该让其在最后-1

对象如果不再使用了,就应该回收它的空间,防止造成内存泄漏

3.2 内存管理研究的内容

1)野指针(僵尸对象)

野指针:

  1)定义的指针变量没有初始化

  2)指向的空间已经被释放了

2)内存泄漏

内存泄漏:

Person *p = [Person new];

//变量p存储在栈区

//[Person new]; 对象存储在堆区

如果栈区的p已经被回收,而堆区的空间还没有释放,堆区的空间就造成了泄漏

--4-- 单对象内存管理

4.1 单个对象的野指针问题

野指针错误:访问了一块不可再用的内存

僵尸对象:所占的内存已经被回收的对象,僵尸对象不能再被使用。(默认情况下Xcode为了提高编码效率,没有开启僵尸对象检测。若要检测,需先打开僵尸对象检测)

空指针:没有指向任何对象的指针: Person *p = nil;

   给空指针发送消息不会有反应

4.2 避免使用僵尸对象的方法

为了防止不小心调用了僵尸对象,可以将对象赋值nil(对象的空值)

Dog *dog = [[Dog alloc] init];

[dog release]; //引用计数-1

dog = nil; //空指针

[dog retain]; //给nil发送任何消息,都不会有效果。所以僵尸对象检测不会报错

4.3 对象的内存泄漏

情景1:

//创建完成使用后,没有release

Dog *d = [Dog new]; //1
//如果对象没有被回收,就造成了内存泄漏

情景2:

没有遵守内存管理的原则

Dog *d = [Dog new]; //引用计数为1

[d retain]; // 计数+1 = 2

[d release]; //计数 - 1 = 1
//计数不为0,不能被回收

情景3:

不当的使用了nil

Dog *d = [Dog new];

d = nil; //指针指向nil

[d run]; //nil run不会有效果

[d relese]; //nil release

//而此处不能回收,因为Dog对象的引用计数还是1

情景4:

在方法中对传入的对象进行了retain,而调用时没有release

- (BOOL)compareColorWithOther:(Dog *)dog{

    [dog retain]; //此处对象引用计数 + 1

    return YES;

}

//而如果在后面没有相应的release,则同样会造成内存泄漏

--5-- 多个对象内存管理

5.1 多个对象的野指针问题

/**
情景:
Person对象拥有一个Car的对象属性
*/
Person *person = [[Person alloc] init]; //引用计数为1
Car *car = [[Car alloc] init]; //计数为1
        
car.speed = ; //car对象拥有速度属性
        
person.car = car; //将Person对象属性赋值为car
        
[car release]; //car对象计数释放一次 - 1 -->变成了0 [person goByCar]; //此时car变成了一个僵尸对象-->car已经是野指针。但是goByCar中还使用着car对象,这样开启检测后就会抛出运行时错误

5.2 多个对象内存泄漏问题

针对 5.1 中存在的问题。

如果在[car  release]之后,不小心再使用了car对象,就会造成野指针访问错误。所以此处解决这个问题的方法是,在Person类中重写setter方法,在setter方法中将传过来的car对象retain一次。

这样,调用时,[car release]后还可以使用car对象,并在Person的dealloc中将_car对象release一次。

只是这样的解决方法还是不严谨的,正确的写法应该是下面 6.2 中的写法。

--6-- set方法内存管理

6.1 set方法存在的问题

原对象无法释放造成了内存泄漏

根据内存管理的原则:

谁创建,谁release

Car *car = [[Car alloc] init];

Car *bmw = [[Car alloc] init]; //新对象

//谁创建,谁release
[bmw release];
[car release]; 

按照 5.2 中的写法,直接在setter方法中将对象的引用计数+1。但像这样有两个对象时,那么旧对象就不能释放了,因为旧对象在setter中之前就retain了一次,变成了2,而在Person中只是将包含的_car对象release一次,这样旧对象就又造成了内存泄漏。

所以setter中正确的写法应该是这样:

- (void)setCar:(Car *)car{

    if (_car != car){ //如果是新的对象,则先把旧的对象release一次,再将新的对象赋值_car

    [_car release]; //初次是[nil release];

    _car = [car retain]; //再让新的对象引用计数+1

    }

}

Person的dealloc中,只要让成员对象release一次就行了

- (void)dealloc{

    [_car release]; //relase成员属性

    NSLog(@"Person被回收");

    [super dealloc];

}

6.2 不同类型的setter写法

1)基本数据类型:直接赋值(因为基本数据类型由系统回收)

//基本数据类型直接赋值即可
//int float double long struct enum -(void)setAge:(int)age { _age = age; }

2)OC对象类型

//对象类型需手动释放其内存
-(void)setCar:(Car *)car { //1.先判断是不是新传进来的对象 if (car != _car){ //2.对旧对象做一次release [_car release]; //若没有旧对象,则没有影响 //3.对新对象做一次retain,并且赋值给实例变量 _car = [car retain]; } }

总结:

setter的内存管理:

原则:如果在一个类中有其他类的对象(关联关系),在setter中,要判断是否是同一个对象。如果是不同对象则先release旧值,再retain新值。

--7-- @autoreleasepool

7.1 @autoreleasepool是什么

autoreleasepool自动释放池

(1)在iOS程序运行过程中,会创建无数个“池子”,这些“池子”以栈结构的形式存在。

(2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中 。

自动释放池的创建方式

1)iOS5.0以前的创建方式

NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];

……………….

[pool release];//[pool drain];用于mac

2)iOS5.0以后

@autoreleasepool

{ //开始代表创建自动释放池

  //

} //结束代表销毁自动释放池

autorelease

是一种支持引用计数的内存管理方式

它可以暂时的保存某个对象(object),然后在内存池自己的排干(drain)的时候对其中的每个对象发送release消息。

注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。可以用该方法来保存某个对象,但也要注意保存之后要释放该对象。

7.2 为什么会有autorelease

OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放

考虑这种情况,如果一个方法需要返回一个新建的对象,该对象何时释放?

方法内部是不会写release来释放对象的,因为这样做会将对象立即释放而返回一个空对象:调用也不会主动释放该对象的,因为调用者遵循“谁申请,谁释放”的原则,那么这个时候,就发生了内存泄露。

不使用autorelease存在的问题

针对这种情况,Objective-C的设计了autorelease,既能确保对象能正确释放,又能返回有效的对象。

使用autorelease的好处

(1)不需要再关心对象释放的时间

(2)不需要再关心什么时候调用release

7.3 autorelease基本用法

基本用法

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

(2)当自动释放池被销毁时,会对池中的所有对象发送一次release

(3)返回对象本身

(4)调用完autorelease方法后,对象的计数器不受影响(销毁时影响)

在autorelease的模式下,下述写法是合理的,即可以正确返回结果,也不会造成内存泄露

ClassA *Func1(){

Class *obj=[[ClassA alloc]init autorelease];

return obj;

}
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
@autoreleasepool { //注意:加入到自动释放池中以后,引用计数不会变化
[p autorelease]; //把对象p加入到自动释放池中
NSLog(@"p.retainCount = %lu", p.retainCount); //1
//[p release]; //无需手动release
}
return ;
}

重写delloc

@implementation Person

//重写dealloc
- (void)dealloc{
NSLog(@"%@对象释放", self);
[super dealloc];
}
@end

输出-->

7.4 autorelease的原理

autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。

7.5 autorelease什么时候被释放

对于autorelease pool本身,会在如下两个条件发生时候被释放(详细信息请参见第5条)

(1)手动释放Autorelease pool

(2)Runloop结束后自动释放

对于autorelease pool内部的对象

在引用计数的retain==0的时候释放。

release和autorelease pool的drain都会触发retain–事件。

7.6 autorelease使用注意

1)并不是放到自动释放池代码中,都会自动加入到自动释放池

 @autoreleasepool {
// 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
Person *p = [[Person alloc] init];
[p run];
}

2)在自动释放池的外部发送autorelease 不会被加入到自动释放池中

autorelease是一个方法,只有在自动释放池中调用才有效。

@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会加入释放池
Person *p = [[[Person alloc] init] autorelease]; //没有写在自动释放池内,写在了自动释放池外
[p run]; // 正确写法1
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
} // 正确写法2
Person *p = [[Person alloc] init];
@autoreleasepool {
[p autorelease];
}

自动释放池的嵌套使用

  • 自动释放池是以栈(栈结构)的形式存在的
  • 由于栈只有一个入口, 所以调用autorelease会将对象放到栈顶的自动释放池
    •   >栈顶是离调用autorelease方法最近的自动释放池
@autoreleasepool { // 栈底自动释放池

    @autoreleasepool {

        @autoreleasepool { // 栈顶自动释放池

        Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
  • 自动释放池中不适宜放占用内存比较大的对象
    • 尽量要避免将大内存对象加入释放池,因为释放池的延迟释放机制,会使其一直常驻内存
    • 不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升
    // 内存峰值会上升
@autoreleasepool {
for (int i = ; i < ; ++i) {
Person *p = [[[Person alloc] init] autorelease];
}
} //可以使用下面的方法
// 内存不会暴涨
for (int i = ; i < ; ++i) {
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
} //创建完一个对象就释放了
}

【写在结尾:】

『学习如逆水行舟,不进则退』

OC-内存管理的更多相关文章

  1. OC 内存管理机制总结

    OC 内存管理机制总结 一:OC内存管理机制目前分为两块,其一自动内存管理机制,其二手动内存管理机制: 1.首先我们从自动内存管理机制讲起: 1)什么是自动内存管理机制,自动内存管理机制就是程序中所创 ...

  2. OC内存管理基础

    OC 内存管理基础 一. retain和release基本使用 使用注意: 1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作) 2.你不想再使用(占用)某个对象,就 ...

  3. QF——OC内存管理详解

    堆的内存管理: 我们所说的内存管理,其实就是堆的内存管理.因为栈的内存会自动回收,堆的内存需要我们手动回收. 栈中一般存储的是基本数据类型变量和指向对象的指针(对象的引用),而真实的对象存储在堆中.因 ...

  4. OC内存管理-OC笔记

    内存管理细节:http://blog.sina.com.cn/s/blog_814ecfa90102vus2.html 学习目标 1.[理解]内存管理 2.[掌握]第一个MRC程序 3.[掌握]内存管 ...

  5. OC内存管理-黄金法则

    1.内存管理-黄金法则 The basic rule to apply is everything that increases the reference counter with alloc, [ ...

  6. OC内存管理总结,清晰明了!

    <span style="font-size:18px;">OC内存管理 一.基本原理 (一)为什么要进行内存管理. 由于移动设备的内存极其有限.所以每一个APP所占的 ...

  7. 31 (OC)* 内存管理

    31 (OC)  内存管理 一:内存管理黄金法则. 如果对一个对象使用了alloc.[Mutable]copy,retain,那么你必须使用相应的realease或者autorelease 二:内存管 ...

  8. iOS学习17之OC内存管理

    1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄 ...

  9. 【0 - 1】OC内存管理

    一.内存管理概述 垃圾回收机制(GC):由系统管理内存,程序员不需要管理. OC中的垃圾回收:在OC2.0版加入垃圾回收. OC与iOS:OC有垃圾回收机制,但是iOS屏蔽了这个功能.原因:iOS运行 ...

  10. OC内存管理(ARC)

    1.什么是ARC Automatic Reference Counting,自动引用计数,即ARC,可以说是WWDC2011和iOS5所引入 的最大的变革和最激动人心的变化.ARC是新的LLVM 3. ...

随机推荐

  1. rabbitmq method之basic.consume

    basic.consume指的是channel在 某个队列上注册消费者,那在这个队列有消息来了之后,就会把消息转发到给此channel处理,如果 这个队列有多个消费者,则会采用轮转的方式将消息分发给消 ...

  2. django--主要部分

    django URL  URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表:你就是以这种方式告诉Django,对于这个U ...

  3. Magento table rates表运费设置

    在magento中集成了Table rate表运费,这种运输方式.表运费就是我们自己写个运费表,根据距离和商品重量设置运费,制做成一张csv格式的表,导入到magento中,来实现运费的控制. 在我的 ...

  4. 申请使用aws的一些笔记

    1. 申请可以使用asw.amazon.com/cn/,这个界面虽然是中文的,但是申请的是海外的aws. 2. 审核后会收到如下的一封邮件: 3. 剩下创建EC2和RDS的过程可以参考http://w ...

  5. C学习笔记 知识集锦(二)

     1. 数组和指针 2. 字符串赋值 3. memset&memcpy 4. 机器数和真值,原码,反码和补码 5. 文件指针和文件描述符 6. 内存泄露和内存损坏 7. 什么是不可移植的程序 ...

  6. RabbitMQ学习总结 第七篇:RCP(远程过程调用协议)

    在http://www.cnblogs.com/leocook/p/mq_rabbitmq_2.html 这篇博文中我们实现了怎么去使用work queue来把比较耗时的任务分散给多个worker. ...

  7. 基于spring的aop实现读写分离与事务配置

    项目开发中经常会遇到读写分离等多数据源配置的需求,在Java项目中可以通过Spring AOP来实现多数据源的切换. 一.Spring事务开启流程 Spring中通常通过@Transactional来 ...

  8. cygwin 安装apt-cyg命令

    googlecode关闭了.没法用wget安装apt-cyg 找了半天只有github里有个方法可行: apt-cyg is a simple script. To install: lynx -so ...

  9. [原创]java WEB学习笔记103:Spring学习---Spring Bean配置:基于注解的方式(基于注解配置bean,基于注解来装配bean的属性)

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  10. 网络请求出错:The resource could not be loaded because the App Transport

    Google后查证,iOS9引入了新特性App Transport Security (ATS).详情:App Transport Security (ATS) 新特性要求App内访问的网络必须使用H ...